feat(commands): added basic command handler
This commit is contained in:
parent
6b22ebdb9c
commit
7ad0d4bae4
9 changed files with 174 additions and 22 deletions
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"token": "",
|
||||
"logLevel": "info"
|
||||
"logLevel": "",
|
||||
"prefix": ""
|
||||
}
|
13
package.json
13
package.json
|
@ -156,6 +156,16 @@
|
|||
"license-header.txt"
|
||||
],
|
||||
"no-case-declarations": "off",
|
||||
"keyword-spacing": ["error", {"overrides": {
|
||||
"if": {
|
||||
"before": false,
|
||||
"after": false
|
||||
},
|
||||
"catch": {
|
||||
"before": true,
|
||||
"after": false
|
||||
}
|
||||
}}],
|
||||
"@typescript-eslint/no-non-null-assertion": "off"
|
||||
},
|
||||
"reportUnusedDisableDirectives": true
|
||||
|
@ -186,6 +196,7 @@
|
|||
"@": "dist",
|
||||
"@src": "dist/src",
|
||||
"@lib": "dist/src/lib",
|
||||
"@utils": "dist/src/lib/utils/"
|
||||
"@utils": "dist/src/lib/utils/",
|
||||
"@structures": "dist/src/lib/structures"
|
||||
}
|
||||
}
|
||||
|
|
57
src/index.ts
57
src/index.ts
|
@ -23,10 +23,16 @@ import config from 'config';
|
|||
import chalk from 'chalk';
|
||||
import { Validator } from 'jsonschema';
|
||||
import { DateTime } from 'luxon';
|
||||
import { Client } from 'discord.js';
|
||||
import { debug, error, info, verbose } from '@utils/logger';
|
||||
import { Client, Collection } from 'discord.js';
|
||||
import { debug, error, fatal, info, verbose } from '@utils/logger';
|
||||
import { ELoggingScope } from '@utils/types';
|
||||
import { Defaults } from '@utils/defaults';
|
||||
import { walkDir } from '@utils/utils';
|
||||
|
||||
import type Command from '@structures/command';
|
||||
|
||||
let isBotReady = false;
|
||||
const commands: Collection<string, Command> = new Collection();
|
||||
|
||||
info('Starting bot... Please wait!', ELoggingScope.Startup);
|
||||
debug('Checking config JSON schema', ELoggingScope.Startup);
|
||||
|
@ -36,7 +42,7 @@ const mergedConfig = config.util.extendDeep(Defaults.config, config.util.loadFil
|
|||
const schemaValidator = new Validator();
|
||||
const validate = schemaValidator.validate(mergedConfig, Defaults.configSchema);
|
||||
|
||||
if (validate.valid) {
|
||||
if(validate.valid) {
|
||||
debug('Config matches JSON schema', ELoggingScope.Startup);
|
||||
} else {
|
||||
// Manually send fatal message in case someone messes up the logLevel config
|
||||
|
@ -53,7 +59,7 @@ if (validate.valid) {
|
|||
}
|
||||
|
||||
figlet('Argon Bot', (err, data) => {
|
||||
if (err) error(`Figlet encountered an error!\n${err.message}`);
|
||||
if(err) error(`Figlet encountered an error!\n${err.message}`);
|
||||
|
||||
info(gradient.rainbow.multiline(`\n${data}`), ELoggingScope.Startup);
|
||||
});
|
||||
|
@ -62,11 +68,50 @@ const client = new Client({
|
|||
intents: ['GUILDS', 'GUILD_MESSAGES'],
|
||||
});
|
||||
|
||||
client.on('ready', () => {
|
||||
client.on('message', (msg) => {
|
||||
if(!isBotReady) return;
|
||||
|
||||
if(msg.author.bot) return;
|
||||
if(!msg.content.startsWith(config.get('prefix'))) return;
|
||||
|
||||
const args = msg.content.slice((config.get('prefix') as string).length).trim().split(/ +/);
|
||||
const command = args.shift()!.toLowerCase();
|
||||
|
||||
const findCommand = commands.find((com) => com.options.name === command);
|
||||
|
||||
if(!findCommand) return;
|
||||
else findCommand.run(msg, ...args);
|
||||
});
|
||||
|
||||
client.on('ready', async () => {
|
||||
info('Loading commands...', ELoggingScope.Startup);
|
||||
|
||||
try {
|
||||
const files = await walkDir(`${__dirname}/commands`);
|
||||
|
||||
files?.forEach(async (file) => {
|
||||
if(file.endsWith('js')) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const fileCommand = require(file).default;
|
||||
|
||||
const command = new fileCommand();
|
||||
|
||||
commands.set(command.options.name, command);
|
||||
|
||||
debug(`Loaded ${command.options.name}`, ELoggingScope.Startup);
|
||||
}
|
||||
});
|
||||
|
||||
info(`Finished loading commands! Found ${commands.size} commands.`, ELoggingScope.Startup);
|
||||
} catch(err) {
|
||||
fatal(`An error has occurred while attempting to load command files! Please see error below\n${err.message}`, ELoggingScope.Startup);
|
||||
}
|
||||
|
||||
info('Bot is ready!', ELoggingScope.Startup);
|
||||
debug(`Total number of Servers: ${client.guilds.cache.size}`, ELoggingScope.Startup);
|
||||
debug(`Total number of Users: ${client.users.cache.size}`, ELoggingScope.Startup);
|
||||
|
||||
info('Bot is ready!', ELoggingScope.Startup);
|
||||
isBotReady = true;
|
||||
});
|
||||
|
||||
client.on('raw', (payload) => {
|
||||
|
|
30
src/lib/structures/command.ts
Normal file
30
src/lib/structures/command.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* This file is part of ArgonBot
|
||||
*
|
||||
* ArgonBot is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ArgonBot is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ArgonBot. If not, see <https: //www.gnu.org/licenses/>.
|
||||
*/
|
||||
import type { Client, Message } from 'discord.js';
|
||||
import type { ICommandOptions } from '@utils/types';
|
||||
|
||||
export default abstract class Command {
|
||||
public readonly client: Client;
|
||||
public readonly options: ICommandOptions
|
||||
|
||||
public constructor(client: Client, options: ICommandOptions) {
|
||||
this.client = client;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public abstract run(message: Message, ...args:string[]): void;
|
||||
}
|
|
@ -19,9 +19,7 @@ export const Defaults = {
|
|||
logLevel: 'info',
|
||||
},
|
||||
configSchema: {
|
||||
$id: 'http://example.com/example.json',
|
||||
$schema: 'http://json-schema.org/draft-07/schema',
|
||||
required: ['token', 'logLevel'],
|
||||
required: ['token', 'logLevel', 'prefix'],
|
||||
type: 'object',
|
||||
properties: {
|
||||
token: {
|
||||
|
@ -40,6 +38,10 @@ export const Defaults = {
|
|||
'fatal',
|
||||
],
|
||||
},
|
||||
prefix: {
|
||||
$id: '#/properties/prefix',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
|
|
|
@ -70,8 +70,8 @@ export function verbose(message: string, scope?: ELoggingScope): void {
|
|||
const splitMultiline = message.split('\n');
|
||||
|
||||
splitMultiline.forEach((val) => {
|
||||
if (verboseLevel)
|
||||
if (scope)
|
||||
if(verboseLevel)
|
||||
if(scope)
|
||||
console.log(chalk`{grey (${date})} {magenta.bold ${scope}} {white.bold [VERBOSE]}: {white ${val}}`);
|
||||
else
|
||||
console.log(chalk`{grey (${date})} {white.bold [VERBOSE]}: {white ${val}}`);
|
||||
|
@ -83,8 +83,8 @@ export function debug(message: string, scope?: ELoggingScope): void {
|
|||
const splitMultiline = message.split('\n');
|
||||
|
||||
splitMultiline.forEach((val) => {
|
||||
if (debugLevel)
|
||||
if (scope)
|
||||
if(debugLevel)
|
||||
if(scope)
|
||||
console.log(chalk`{grey (${date})} {magenta.bold ${scope}} {blue.bold [DEBUG]}: {blue ${val}}`);
|
||||
else
|
||||
console.log(chalk`{grey (${date})} {blue.bold [DEBUG]}: {blue ${val}}`);
|
||||
|
@ -96,7 +96,7 @@ export function info(message: string, scope?: ELoggingScope): void {
|
|||
const splitMultiline = message.split('\n');
|
||||
|
||||
splitMultiline.forEach((val) => {
|
||||
if (infoLevel)
|
||||
if(infoLevel)
|
||||
if(scope)
|
||||
console.log(chalk`{grey (${date})} {magenta.bold ${scope}} {green.bold [INFO]}: {green ${val}}`);
|
||||
else
|
||||
|
@ -109,8 +109,8 @@ export function warn(message: string, scope?: ELoggingScope): void {
|
|||
const splitMultiline = message.split('\n');
|
||||
|
||||
splitMultiline.forEach((val) => {
|
||||
if (warnLevel)
|
||||
if (scope)
|
||||
if(warnLevel)
|
||||
if(scope)
|
||||
console.log(chalk`{grey (${date})} {magenta.bold ${scope}} {yellow.bold [WARN]}: {yellow ${val}}`);
|
||||
else
|
||||
console.log(chalk`{grey (${date})} {yellow.bold [WARN]}: {yellow ${val}}`);
|
||||
|
@ -122,8 +122,8 @@ export function error(message: string, scope?: ELoggingScope): void {
|
|||
const splitMultiline = message.split('\n');
|
||||
|
||||
splitMultiline.forEach((val) => {
|
||||
if (errorLevel)
|
||||
if (scope)
|
||||
if(errorLevel)
|
||||
if(scope)
|
||||
console.log(chalk`{grey (${date})} {magenta.bold ${scope}} {bold.underline.rgb(255, 165, 0) [ERROR]}: {underline.rgb(255, 165, 0) ${val}}`);
|
||||
else
|
||||
console.log(chalk`{grey (${date})} {bold.underline.rgb(255, 165, 0) [ERROR]}: {underline.rgb(255, 165, 0) ${val}}`);
|
||||
|
@ -135,8 +135,8 @@ export function fatal(message: string, scope?: ELoggingScope): void {
|
|||
const splitMultiline = message.split('\n');
|
||||
|
||||
splitMultiline.forEach((val) => {
|
||||
if (fatalLevel)
|
||||
if (scope)
|
||||
if(fatalLevel)
|
||||
if(scope)
|
||||
console.log(chalk`{grey (${date})} {magenta.bold ${scope}} {red.bold.underline [FATAL]}: {red.underline ${val}}`);
|
||||
else
|
||||
console.log(chalk`{grey (${date})} {red.bold.underline [FATAL]}: {red.underline ${val}}`);
|
||||
|
|
|
@ -23,5 +23,10 @@ export enum ELoggingScope {
|
|||
}
|
||||
|
||||
// Interfaces
|
||||
export interface ICommandOptions {
|
||||
name: string;
|
||||
shortDescription: string;
|
||||
extendedDescription: string;
|
||||
}
|
||||
|
||||
// Type Aliases
|
||||
|
|
55
src/lib/utils/utils.ts
Normal file
55
src/lib/utils/utils.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* This file is part of ArgonBot
|
||||
*
|
||||
* ArgonBot is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ArgonBot is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ArgonBot. If not, see <https: //www.gnu.org/licenses/>.
|
||||
*/
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export const walkDir = async (dir: string): Promise<string[]> => {
|
||||
let results: string[] = [];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readdir(dir, (err, list) => {
|
||||
if(err) return reject(err);
|
||||
|
||||
let pending = list.length;
|
||||
|
||||
if(!pending) return resolve(results);
|
||||
|
||||
list.forEach((file) => {
|
||||
file = path.resolve(dir, file);
|
||||
|
||||
fs.stat(file, async (err, stat) => {
|
||||
if(err) return reject(err);
|
||||
|
||||
if(stat && stat.isDirectory()) {
|
||||
try {
|
||||
const dir = await walkDir(file);
|
||||
|
||||
results = results.concat(dir as unknown as string);
|
||||
|
||||
if(!--pending) resolve(results);
|
||||
} catch(err) {
|
||||
reject(err);
|
||||
}
|
||||
} else {
|
||||
results.push(file);
|
||||
if(!--pending) resolve(results);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
|
@ -65,6 +65,9 @@
|
|||
],
|
||||
"@utils/*": [
|
||||
"src/lib/utils/*"
|
||||
],
|
||||
"@structures/*": [
|
||||
"src/lib/structures/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
Reference in a new issue