Merge VylBot-Core

This commit is contained in:
Ethan Lane 2021-12-21 18:02:27 +00:00
parent 45d871fbf7
commit 8ca1800da5
Signed by: Vylpes
GPG key ID: EED233CC06D12504
39 changed files with 3316 additions and 146 deletions

View file

@ -13,8 +13,6 @@ BOT_AUTHOR=Vylpes
BOT_DATE=28 Nov 2021 BOT_DATE=28 Nov 2021
BOT_OWNERID=147392775707426816 BOT_OWNERID=147392775707426816
CORE_VER=2.0.2
FOLDERS_COMMANDS=src/commands FOLDERS_COMMANDS=src/commands
FOLDERS_EVENTS=src/events FOLDERS_EVENTS=src/events

View file

@ -1,61 +0,0 @@
{
"token": "",
"prefix": "v!",
"commands": [
"commands"
],
"events": [
"events"
],
"about": {
"description": "Discord Bot for Vylpes' Den",
"version": "2.2",
"core-ver": "1.0.4",
"author": "Vylpes",
"date": "23/11/2021"
},
"ban": {
"modrole": "Moderator",
"logchannel": "mod-logs"
},
"clear": {
"modrole": "Moderator",
"logchannel": "mod-logs"
},
"eval": {
"ownerid": "147392775707426816"
},
"kick": {
"modrole": "Moderator",
"logchannel": "mod-logs"
},
"mute": {
"modrole": "Moderator",
"logchannel": "mod-logs",
"muterole": "Muted"
},
"partner": {
"adminrole": "Admin",
"partnersfile": "data/partner/partner.json"
},
"rules": {
"adminrole": "Admin",
"rulesfile": "data/rules/rules.json"
},
"unmute": {
"modrole": "Moderator",
"logchannel": "mod-logs",
"muterole": "Muted"
},
"warn": {
"modrole": "Moderator",
"logchannel": "mod-logs"
},
"role": {
"assignable": [
"Notify",
"VotePings",
"ProjectUpdates"
]
}
}

5
jest.config.js Normal file
View file

@ -0,0 +1,5 @@
/** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};

View file

@ -6,7 +6,8 @@
"typings": "./dist", "typings": "./dist",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"start": "ts-node ./src/vylbot" "start": "ts-node ./src/vylbot",
"test": "jest"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -17,10 +18,14 @@
"bugs": "https://github.com/Vylpes/vylbot-app/issues", "bugs": "https://github.com/Vylpes/vylbot-app/issues",
"homepage": "https://github.com/Vylpes/vylbot-app", "homepage": "https://github.com/Vylpes/vylbot-app",
"dependencies": { "dependencies": {
"@types/jest": "^27.0.3",
"discord.js": "12.5.3",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"emoji-regex": "^9.2.0", "emoji-regex": "^9.2.0",
"jest": "^27.4.5",
"jest-mock-extended": "^2.0.4",
"random-bunny": "^2.0.0", "random-bunny": "^2.0.0",
"vylbot-core": "^2.0.3" "ts-jest": "^27.1.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^16.11.10", "@types/node": "^16.11.10",

32
src/client/client.ts Normal file
View file

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

75
src/client/events.ts Normal file
View file

@ -0,0 +1,75 @@
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.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) {
return {
valid: false,
message: res.message,
};
}
return {
valid: true,
context: {
prefix: prefix,
name: name,
args: args,
message: message,
},
};
}
return {
valid: false,
message: "Message was not a command, ignoring.",
}
}
// Emit when bot is logged in and ready to use
public onReady() {
console.log("Ready");
}
}

132
src/client/util.ts Normal file
View file

@ -0,0 +1,132 @@
// 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";
import { ICommandContext } from "../contracts/ICommandContext";
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 message",
};
const disabledCommands = process.env.COMMANDS_DISABLED?.split(',');
if (disabledCommands?.find(x => x == name)) {
message.reply(process.env.COMMANDS_DISABLED_MESSAGE || "This command is disabled.");
return {
valid: false,
message: "Command is disabled",
};
}
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`).default;
const command = new commandFile() 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`,
};
}
}
const context: ICommandContext = {
name: name,
args: args,
message: message,
}
// Run the command and pass the command context with it
command.execute(context);
return {
valid: true,
context: {
name: name,
args: args,
message: message,
}
}
} else {
return {
valid: false,
message: "File does not exist",
}
}
} else {
return {
valid: false,
message: "Command folder does not exist",
}
}
}
// Load the events
loadEvents(client: Client): IUtilResponse {
const folder = process.env.FOLDERS_EVENTS;
if (existsSync(`${process.cwd()}/${folder}/`)) {
const eventFiles = readdirSync(`${process.cwd()}/${folder}/`);
for (let i = 0; i < eventFiles.length; i++) {
if (eventFiles[i].includes('.ts')) {
const eventName = eventFiles[i].split('.')[0];
const file = require(`${process.cwd()}/${folder}/${eventName}.ts`);
const event = new file[eventName]() as Event;
// Load events
client.on('channelCreate', event.channelCreate);
client.on('channelDelete', event.channelDelete);
client.on('channelUpdate', event.channelUpdate);
client.on('guildBanAdd', event.guildBanAdd);
client.on('guildBanRemove', event.guildBanRemove);
client.on('guildCreate', event.guildCreate);
client.on('guildMemberAdd', event.guildMemberAdd);
client.on('guildMemberRemove', event.guildMemberRemove);
client.on('guildMemberUpdate', event.guildMemberUpdate);
client.on('message', event.message);
client.on('messageDelete', event.messageDelete);
client.on('messageUpdate', event.messageUpdate);
client.on('ready', event.ready);
}
}
return {
valid: true,
}
} else {
return {
valid: false,
message: "Event folder does not exist",
}
}
}
}

View file

@ -1,5 +1,6 @@
import { Command, ICommandContext } from "vylbot-core"; import { ICommandContext } from "../contracts/ICommandContext";
import PublicEmbed from "../helpers/PublicEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
export default class About extends Command { export default class About extends Command {
constructor() { constructor() {
@ -10,7 +11,6 @@ export default class About extends Command {
public override execute(context: ICommandContext) { public override execute(context: ICommandContext) {
const embed = new PublicEmbed(context, "About", "") const embed = new PublicEmbed(context, "About", "")
.addField("Version", process.env.BOT_VER) .addField("Version", process.env.BOT_VER)
.addField("VylBot Core", process.env.CORE_VER)
.addField("Author", process.env.BOT_AUTHOR) .addField("Author", process.env.BOT_AUTHOR)
.addField("Date", process.env.BOT_DATE); .addField("Date", process.env.BOT_DATE);

View file

@ -1,8 +1,9 @@
import { Command, ICommandContext } from "vylbot-core"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import ErrorEmbed from "../helpers/ErrorEmbed";
import ErrorMessages from "../constants/ErrorMessages"; import ErrorMessages from "../constants/ErrorMessages";
import LogEmbed from "../helpers/LogEmbed"; import LogEmbed from "../helpers/embeds/LogEmbed";
import PublicEmbed from "../helpers/PublicEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
import { ICommandContext } from "../contracts/ICommandContext";
export default class Bane extends Command { export default class Bane extends Command {
constructor() { constructor() {

View file

@ -1,7 +1,8 @@
import { Command, ICommandContext } from "vylbot-core"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import ErrorEmbed from "../helpers/ErrorEmbed";
import { TextChannel } from "discord.js"; import { TextChannel } from "discord.js";
import PublicEmbed from "../helpers/PublicEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
import { ICommandContext } from "../contracts/ICommandContext";
export default class Clear extends Command { export default class Clear extends Command {
constructor() { constructor() {

View file

@ -1,6 +1,7 @@
import { Command, ICommandContext } from "vylbot-core"; import { ICommandContext } from "../contracts/ICommandContext";
import ErrorEmbed from "../helpers/ErrorEmbed"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import PublicEmbed from "../helpers/PublicEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
export default class Evaluate extends Command { export default class Evaluate extends Command {
constructor() { constructor() {

View file

@ -1,8 +1,9 @@
import { existsSync, readdirSync } from "fs"; import { existsSync, readdirSync } from "fs";
import { Command, ICommandContext } from "vylbot-core"; import { ICommandContext } from "../contracts/ICommandContext";
import ErrorEmbed from "../helpers/ErrorEmbed"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import PublicEmbed from "../helpers/PublicEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed";
import StringTools from "../helpers/StringTools"; import StringTools from "../helpers/StringTools";
import { Command } from "../type/command";
interface ICommandData { interface ICommandData {
Exists: boolean; Exists: boolean;

View file

@ -1,8 +1,9 @@
import { Command, ICommandContext } from "vylbot-core";
import ErrorMessages from "../constants/ErrorMessages"; import ErrorMessages from "../constants/ErrorMessages";
import ErrorEmbed from "../helpers/ErrorEmbed"; import { ICommandContext } from "../contracts/ICommandContext";
import LogEmbed from "../helpers/LogEmbed"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import PublicEmbed from "../helpers/PublicEmbed"; import LogEmbed from "../helpers/embeds/LogEmbed";
import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
export default class Kick extends Command { export default class Kick extends Command {
constructor() { constructor() {

View file

@ -1,8 +1,9 @@
import { Command, ICommandContext } from "vylbot-core";
import ErrorMessages from "../constants/ErrorMessages"; import ErrorMessages from "../constants/ErrorMessages";
import ErrorEmbed from "../helpers/ErrorEmbed"; import { ICommandContext } from "../contracts/ICommandContext";
import LogEmbed from "../helpers/LogEmbed"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import PublicEmbed from "../helpers/PublicEmbed"; import LogEmbed from "../helpers/embeds/LogEmbed";
import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
export default class Mute extends Command { export default class Mute extends Command {
constructor() { constructor() {

View file

@ -1,6 +1,7 @@
import { Command, ICommandContext } from "vylbot-core"; import { ICommandContext } from "../contracts/ICommandContext";
import ErrorEmbed from "../helpers/ErrorEmbed"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import PublicEmbed from "../helpers/PublicEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
export default class Poll extends Command { export default class Poll extends Command {
constructor() { constructor() {

View file

@ -1,7 +1,8 @@
import { Command, ICommandContext } from "vylbot-core"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import ErrorEmbed from "../helpers/ErrorEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed";
import PublicEmbed from "../helpers/PublicEmbed";
import { Role as DiscordRole } from "discord.js"; import { Role as DiscordRole } from "discord.js";
import { Command } from "../type/command";
import { ICommandContext } from "../contracts/ICommandContext";
export default class Role extends Command { export default class Role extends Command {
constructor() { constructor() {

View file

@ -1,7 +1,8 @@
import { existsSync, readFileSync } from "fs"; import { existsSync, readFileSync } from "fs";
import { Command, ICommandContext } from "vylbot-core"; import { ICommandContext } from "../contracts/ICommandContext";
import ErrorEmbed from "../helpers/ErrorEmbed"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import PublicEmbed from "../helpers/PublicEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
interface IRules { interface IRules {
title?: string; title?: string;

View file

@ -1,8 +1,9 @@
import { Command, ICommandContext } from "vylbot-core";
import ErrorMessages from "../constants/ErrorMessages"; import ErrorMessages from "../constants/ErrorMessages";
import ErrorEmbed from "../helpers/ErrorEmbed"; import { ICommandContext } from "../contracts/ICommandContext";
import LogEmbed from "../helpers/LogEmbed"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import PublicEmbed from "../helpers/PublicEmbed"; import LogEmbed from "../helpers/embeds/LogEmbed";
import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
export default class Unmute extends Command { export default class Unmute extends Command {
constructor() { constructor() {

View file

@ -1,7 +1,8 @@
import { Command, ICommandContext } from "vylbot-core"; import { ICommandContext } from "../contracts/ICommandContext";
import ErrorEmbed from "../helpers/ErrorEmbed"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import LogEmbed from "../helpers/LogEmbed"; import LogEmbed from "../helpers/embeds/LogEmbed";
import PublicEmbed from "../helpers/PublicEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
export default class Warn extends Command { export default class Warn extends Command {
constructor() { constructor() {

View file

@ -0,0 +1,4 @@
export interface IBaseResponse {
valid: boolean;
message?: string;
}

View file

@ -0,0 +1,7 @@
import { Message } from "discord.js";
export interface ICommandContext {
name: string;
args: string[];
message: Message;
}

View file

@ -1,6 +1,6 @@
import { Event } from "vylbot-core"; import { Event } from "../type/event";
import { GuildMember } from "discord.js"; import { GuildMember } from "discord.js";
import EventEmbed from "../helpers/EventEmbed"; import EventEmbed from "../helpers/embeds/EventEmbed";
import GuildMemberUpdate from "./MemberEvents/GuildMemberUpdate"; import GuildMemberUpdate from "./MemberEvents/GuildMemberUpdate";
export default class MemberEvents extends Event { export default class MemberEvents extends Event {

View file

@ -1,5 +1,5 @@
import { GuildMember } from "discord.js"; import { GuildMember } from "discord.js";
import EventEmbed from "../../helpers/EventEmbed"; import EventEmbed from "../../helpers/embeds/EventEmbed";
export default class GuildMemberUpdate { export default class GuildMemberUpdate {
private _oldMember: GuildMember; private _oldMember: GuildMember;

View file

@ -1,6 +1,6 @@
import { Event } from "vylbot-core"; import { Event } from "../type/event";
import { Message } from "discord.js"; import { Message } from "discord.js";
import EventEmbed from "../helpers/EventEmbed"; import EventEmbed from "../helpers/embeds/EventEmbed";
export default class MessageEvents extends Event { export default class MessageEvents extends Event {
constructor() { constructor() {

View file

@ -1,5 +1,5 @@
import { MessageEmbed } from "discord.js"; import { MessageEmbed } from "discord.js";
import { ICommandContext } from "vylbot-core"; import { ICommandContext } from "../../contracts/ICommandContext";
export default class ErrorEmbed extends MessageEmbed { export default class ErrorEmbed extends MessageEmbed {
private _context: ICommandContext; private _context: ICommandContext;

View file

@ -1,6 +1,4 @@
import { MessageEmbed, TextChannel, User, Guild } from "discord.js"; import { MessageEmbed, TextChannel, User, Guild } from "discord.js";
import ErrorMessages from "../constants/ErrorMessages";
import ErrorEmbed from "./ErrorEmbed";
export default class EventEmbed extends MessageEmbed { export default class EventEmbed extends MessageEmbed {
private _guild: Guild; private _guild: Guild;

View file

@ -1,6 +1,6 @@
import { MessageEmbed, TextChannel, User } from "discord.js"; import { MessageEmbed, TextChannel, User } from "discord.js";
import { ICommandContext } from "vylbot-core"; import ErrorMessages from "../../constants/ErrorMessages";
import ErrorMessages from "../constants/ErrorMessages"; import { ICommandContext } from "../../contracts/ICommandContext";
import ErrorEmbed from "./ErrorEmbed"; import ErrorEmbed from "./ErrorEmbed";
export default class LogEmbed extends MessageEmbed { export default class LogEmbed extends MessageEmbed {

View file

@ -1,5 +1,5 @@
import { MessageEmbed } from "discord.js"; import { MessageEmbed } from "discord.js";
import { ICommandContext } from "vylbot-core"; import { ICommandContext } from "../../contracts/ICommandContext";
export default class PublicEmbed extends MessageEmbed { export default class PublicEmbed extends MessageEmbed {
private _context: ICommandContext; private _context: ICommandContext;

16
src/type/command.ts Normal file
View file

@ -0,0 +1,16 @@
import { Message } from "discord.js";
import { ICommandContext } from "../contracts/ICommandContext";
export class Command {
public _roles: string[];
public _category?: string;
constructor() {
this._roles = [];
}
public execute(context: ICommandContext) {
}
}

55
src/type/event.ts Normal file
View file

@ -0,0 +1,55 @@
import { Channel, Guild, User, GuildMember, Message, PartialDMChannel, PartialGuildMember, PartialMessage } from "discord.js";
export class Event {
public channelCreate(channel: Channel) {
}
public channelDelete(channel: Channel | PartialDMChannel) {
}
public channelUpdate(oldChannel: Channel, newChannel: Channel) {
}
public guildBanAdd(guild: Guild, user: User) {
}
public guildBanRemove(guild: Guild, user: User) {
}
public guildCreate(guild: Guild) {
}
public guildMemberAdd(member: GuildMember) {
}
public guildMemberRemove(member: GuildMember | PartialGuildMember) {
}
public guildMemberUpdate(oldMember: GuildMember | PartialGuildMember, newMember: GuildMember) {
}
public message(message: Message) {
}
public messageDelete(message: Message | PartialMessage) {
}
public messageUpdate(oldMessage: Message | PartialMessage, newMessage: Message | PartialMessage) {
}
public ready() {
}
}

View file

@ -1,4 +1,4 @@
import { CoreClient } from "vylbot-core"; import { CoreClient } from "./client/client";
import * as dotenv from "dotenv"; import * as dotenv from "dotenv";
dotenv.config(); dotenv.config();

View file

@ -0,0 +1,7 @@
import { Command } from "../../../src/type/command";
export default class noCategory extends Command {
constructor() {
super();
}
}

View file

@ -0,0 +1,8 @@
import { Command } from "../../../src/type/command";
export default class normal extends Command {
constructor() {
super();
this._category = "General";
}
}

View file

@ -0,0 +1,8 @@
import { Command } from "../../../src/type/command";
export default class roles extends Command {
constructor() {
super();
this._roles = [ "Moderator" ];
}
}

View file

@ -0,0 +1,5 @@
import { Event } from "../../../src/type/event";
export class normal extends Event {
public override channelCreate() {}
}

139
tests/client/client.test.ts Normal file
View file

@ -0,0 +1,139 @@
import { CoreClient } from "../../src/client/client";
import { Client } from "discord.js";
import * as dotenv from "dotenv";
import { Events } from "../../src/client/events";
import { Util } from "../../src/client/util";
jest.mock("discord.js");
jest.mock("dotenv");
jest.mock("../../src/client/events");
jest.mock("../../src/client/util");
describe('Constructor', () => {
test('Constructor_ExpectSuccessfulInitialisation', () => {
const coreClient = new CoreClient();
expect(coreClient).toBeInstanceOf(Client);
expect(dotenv.config).toBeCalledTimes(1);
expect(Events).toBeCalledTimes(1);
expect(Util).toBeCalledTimes(1);
});
});
describe('Start', () => {
test('Given Env Is Valid, Expect Successful Start', () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
}
const coreClient = new CoreClient();
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',
}
const coreClient = new CoreClient();
expect(() => coreClient.start()).toThrow("BOT_TOKEN is not defined in .env");
});
test('Given BOT_TOKEN Is Empty, Expect Failure', () => {
process.env = {
BOT_TOKEN: '',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
}
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");
});
});

185
tests/client/events.test.ts Normal file
View file

@ -0,0 +1,185 @@
import { Events } from "../../src/client/events";
import { Message, Client, TextChannel, Guild, SnowflakeUtil, DMChannel } from "discord.js";
import { Util } from "../../src/client/util";
jest.mock("../../src/client/util");
beforeEach(() => {
Util.prototype.loadCommand = jest.fn();
});
describe('OnMessage', () => {
test('Given Message Is Valid Expect Message Sent', async () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
};
Util.prototype.loadCommand = jest.fn().mockReturnValue({ valid: true });
const message = {
guild: {},
author: {
bot: false,
},
content: "!test first",
} as unknown as Message;
const events = new Events();
const result = await events.onMessage(message);
expect(result.valid).toBeTruthy();
expect(result.context?.prefix).toBe('!');
expect(result.context?.name).toBe('test');
expect(result.context?.args.length).toBe(1);
expect(result.context?.args[0]).toBe('first');
expect(result.context?.message).toBe(message);
});
test('Given Guild Is Null, Expect Failed Result', async () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
}
Util.prototype.loadCommand = jest.fn().mockReturnValue({ valid: true });
const message = {
guild: null,
author: {
bot: false,
},
content: "!test first",
} as unknown as Message;
const events = new Events();
const result = await events.onMessage(message);
expect(result.valid).toBeFalsy();
expect(result.message).toBe("Message was not sent in a guild, ignoring.");
});
test('Given Author Is A Bot, Expect Failed Result', async () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
}
Util.prototype.loadCommand = jest.fn().mockReturnValue({ valid: true });
const message = {
guild: {},
author: {
bot: true,
},
content: "!test first",
} as unknown as Message;
const events = new Events();
const result = await events.onMessage(message);
expect(result.valid).toBeFalsy();
expect(result.message).toBe("Message was sent by a bot, ignoring.");
});
test('Given Message Content Was Not A Command, Expect Failed Result', async () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
}
Util.prototype.loadCommand = jest.fn().mockReturnValue({ valid: true });
const message = {
guild: {},
author: {
bot: false,
},
content: "This is a standard message",
} as unknown as Message;
const events = new Events();
const result = await events.onMessage(message);
expect(result.valid).toBeFalsy();
expect(result.message).toBe("Message was not a command, ignoring.");
});
test('Given Message Had No Command Name, Expect Failed Result', async () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
}
Util.prototype.loadCommand = jest.fn().mockReturnValue({ valid: true });
const message = {
guild: {},
author: {
bot: false,
},
content: "!",
} as unknown as Message;
const events = new Events();
const result = await events.onMessage(message);
expect(result.valid).toBeFalsy();
expect(result.message).toBe("Command name was not found");
});
test('Given Command Failed To Execute, Expect Failed Result', async () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
}
Util.prototype.loadCommand = jest.fn().mockReturnValue({ valid: false, message: "Command failed" });
const message = {
guild: {},
author: {
bot: false,
},
content: "!test first",
} as unknown as Message;
const events = new Events();
const result = await events.onMessage(message);
expect(result.valid).toBeFalsy();
expect(result.message).toBe("Command failed");
});
});
describe('OnReady', () => {
test('Expect Console Log', () => {
console.log = jest.fn();
const events = new Events();
events.onReady();
expect(console.log).toBeCalledWith("Ready");
});
});

421
tests/client/util.test.ts Normal file
View file

@ -0,0 +1,421 @@
import { Util } from "../../src/client/util";
import { Client, Guild, Message, Role, SnowflakeUtil, TextChannel, User } from "discord.js";
import fs from "fs";
jest.mock("fs");
beforeEach(() => {
fs.existsSync = jest.fn();
});
describe('LoadCommand', () => {
test('Given Successful Exection, Expect Successful Result', () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
}
process.cwd = jest.fn().mockReturnValue("../../tests/__mocks");
fs.existsSync = jest.fn().mockReturnValue(true);
const message = {
member: {
roles: {
cache: {
find: jest.fn().mockReturnValue(true),
}
},
},
reply: jest.fn(),
} as unknown as Message;
const util = new Util();
const result = util.loadCommand("normal", [ "first" ], message);
expect(result.valid).toBeTruthy();
});
test('Given Member Is Null, Expect Failed Result', () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
}
process.cwd = jest.fn().mockReturnValue("../../tests/__mocks");
fs.existsSync = jest.fn().mockReturnValue(true);
const message = {
member: null
} as unknown as Message;
const util = new Util();
const result = util.loadCommand("normal", [ "first" ], message);
expect(result.valid).toBeFalsy();
expect(result.message).toBe("Member is not part of message");
});
test('Given Folder Does Not Exist, Expect Failed Result', () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
}
process.cwd = jest.fn().mockReturnValue("../../tests/__mocks");
fs.existsSync = jest.fn().mockReturnValue(false);
const message = {
member: {
roles: {
cache: {
find: jest.fn().mockReturnValue(true),
}
},
},
reply: jest.fn(),
} as unknown as Message;
const util = new Util();
const result = util.loadCommand("normal", [ "first" ], message);
expect(result.valid).toBeFalsy();
expect(result.message).toBe("Command folder does not exist");
});
test('Given File Does Not Exist, Expect Failed Result', () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
}
process.cwd = jest.fn().mockReturnValue("../../tests/__mocks");
fs.existsSync = jest.fn().mockReturnValueOnce(true)
.mockReturnValue(false);
const message = {
member: {
roles: {
cache: {
find: jest.fn().mockReturnValue(true),
}
},
},
reply: jest.fn(),
} as unknown as Message;
const util = new Util();
const result = util.loadCommand("normal", [ "first" ], message);
expect(result.valid).toBeFalsy();
expect(result.message).toBe("File does not exist");
});
test('Given User Does Have Role, Expect Successful Result', () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
}
process.cwd = jest.fn().mockReturnValue("../../tests/__mocks");
fs.existsSync = jest.fn().mockReturnValue(true);
const message = {
member: {
roles: {
cache: {
find: jest.fn().mockReturnValue(true),
}
},
},
reply: jest.fn(),
} as unknown as Message;
const util = new Util();
const result = util.loadCommand("roles", [ "first" ], message);
expect(result.valid).toBeTruthy();
});
test('Given User Does Not Have Role, Expect Failed Result', () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
}
process.cwd = jest.fn().mockReturnValue("../../tests/__mocks");
fs.existsSync = jest.fn().mockReturnValue(true);
const message = {
member: {
roles: {
cache: {
find: jest.fn().mockReturnValue(false),
}
},
},
reply: jest.fn(),
} as unknown as Message;
const util = new Util();
const result = util.loadCommand("roles", [ "first" ], message);
expect(result.valid).toBeFalsy();
expect(result.message).toBe("You require the `Moderator` role to run this command");
});
test('Given Command Category Is Null, Expect Successful Result', () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
}
process.cwd = jest.fn().mockReturnValue("../../tests/__mocks");
fs.existsSync = jest.fn().mockReturnValue(true);
const message = {
member: {
roles: {
cache: {
find: jest.fn().mockReturnValue(true),
}
},
},
reply: jest.fn(),
} as unknown as Message;
const util = new Util();
const result = util.loadCommand("noCategory", [ "first" ], message);
expect(result.valid).toBeTruthy();
});
test('Given command is set to disabled, Expect command to not fire', () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
COMMANDS_DISABLED: 'normal',
COMMANDS_DISABLED_MESSAGE: 'disabled',
}
process.cwd = jest.fn().mockReturnValue("../../tests/__mocks");
fs.existsSync = jest.fn().mockReturnValue(true);
const message = {
member: {
roles: {
cache: {
find: jest.fn().mockReturnValue(true),
}
},
},
reply: jest.fn(),
} as unknown as Message;
const messageReply = jest.spyOn(message, 'reply');
const util = new Util();
const result = util.loadCommand("normal", [ "first" ], message);
expect(result.valid).toBeFalsy();
expect(result.message).toBe("Command is disabled");
expect(messageReply).toBeCalledWith("disabled");
});
test('Given command COMMANDS_DISABLED_MESSAGE is empty, Expect default message sent', () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
COMMANDS_DISABLED: 'normal',
}
process.cwd = jest.fn().mockReturnValue("../../tests/__mocks");
fs.existsSync = jest.fn().mockReturnValue(true);
const message = {
member: {
roles: {
cache: {
find: jest.fn().mockReturnValue(true),
}
},
},
reply: jest.fn(),
} as unknown as Message;
const messageReply = jest.spyOn(message, 'reply');
const util = new Util();
const result = util.loadCommand("normal", [ "first" ], message);
expect(result.valid).toBeFalsy();
expect(result.message).toBe("Command is disabled");
expect(messageReply).toBeCalledWith("This command is disabled.");
});
test('Given a different command is disabled, Expect command to still fire', () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
COMMANDS_DISABLED: 'anything',
}
process.cwd = jest.fn().mockReturnValue("../../tests/__mocks");
fs.existsSync = jest.fn().mockReturnValue(true);
const message = {
member: {
roles: {
cache: {
find: jest.fn().mockReturnValue(true),
}
},
},
reply: jest.fn(),
} as unknown as Message;
const util = new Util();
const result = util.loadCommand("normal", [ "first" ], message);
expect(result.valid).toBeTruthy();
});
test('Given a different command is disabled with this one, Expect command to not fire', () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
COMMANDS_DISABLED: 'normal,anything,',
}
process.cwd = jest.fn().mockReturnValue("../../tests/__mocks");
fs.existsSync = jest.fn().mockReturnValue(true);
const message = {
member: {
roles: {
cache: {
find: jest.fn().mockReturnValue(true),
}
},
},
reply: jest.fn(),
} as unknown as Message;
const util = new Util();
const result = util.loadCommand("normal", [ "first" ], message);
expect(result.valid).toBeFalsy();
expect(result.message).toBe("Command is disabled");
});
});
describe('LoadEvents', () => {
test('Given Events Are Loaded, Expect Successful Result', () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
}
process.cwd = jest.fn().mockReturnValue("../../tests/__mocks");
fs.existsSync = jest.fn().mockReturnValue(true);
fs.readdirSync = jest.fn().mockReturnValue(["normal.ts"]);
const client = {
on: jest.fn(),
} as unknown as Client;
const util = new Util();
const result = util.loadEvents(client);
const clientOn = jest.spyOn(client, 'on');
expect(result.valid).toBeTruthy();
expect(clientOn).toBeCalledTimes(13);
});
test('Given No Events Found, Expect Successful Result', () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
}
process.cwd = jest.fn().mockReturnValue("../../tests/__mocks");
fs.existsSync = jest.fn().mockReturnValue(true);
fs.readdirSync = jest.fn().mockReturnValue(["normal"]);
const client = {
on: jest.fn(),
} as unknown as Client;
const util = new Util();
const result = util.loadEvents(client);
const clientOn = jest.spyOn(client, 'on');
expect(result.valid).toBeTruthy();
expect(clientOn).toBeCalledTimes(0);
});
test('Given Event Folder Does Not Exist, Expect Failed Result', () => {
process.env = {
BOT_TOKEN: 'TOKEN',
BOT_PREFIX: '!',
FOLDERS_COMMANDS: 'commands',
FOLDERS_EVENTS: 'events',
}
process.cwd = jest.fn().mockReturnValue("../../tests/__mocks");
fs.existsSync = jest.fn().mockReturnValue(false);
fs.readdirSync = jest.fn().mockReturnValue(["normal.ts"]);
const client = {
on: jest.fn(),
} as unknown as Client;
const util = new Util();
const result = util.loadEvents(client);
expect(result.valid).toBeFalsy();
expect(result.message).toBe("Event folder does not exist");
});
});

2176
yarn.lock

File diff suppressed because it is too large Load diff