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": "",
|
"token": "",
|
||||||
"logLevel": "info"
|
"logLevel": "",
|
||||||
|
"prefix": ""
|
||||||
}
|
}
|
13
package.json
13
package.json
|
@ -156,6 +156,16 @@
|
||||||
"license-header.txt"
|
"license-header.txt"
|
||||||
],
|
],
|
||||||
"no-case-declarations": "off",
|
"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"
|
"@typescript-eslint/no-non-null-assertion": "off"
|
||||||
},
|
},
|
||||||
"reportUnusedDisableDirectives": true
|
"reportUnusedDisableDirectives": true
|
||||||
|
@ -186,6 +196,7 @@
|
||||||
"@": "dist",
|
"@": "dist",
|
||||||
"@src": "dist/src",
|
"@src": "dist/src",
|
||||||
"@lib": "dist/src/lib",
|
"@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 chalk from 'chalk';
|
||||||
import { Validator } from 'jsonschema';
|
import { Validator } from 'jsonschema';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { Client } from 'discord.js';
|
import { Client, Collection } from 'discord.js';
|
||||||
import { debug, error, info, verbose } from '@utils/logger';
|
import { debug, error, fatal, info, verbose } from '@utils/logger';
|
||||||
import { ELoggingScope } from '@utils/types';
|
import { ELoggingScope } from '@utils/types';
|
||||||
import { Defaults } from '@utils/defaults';
|
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);
|
info('Starting bot... Please wait!', ELoggingScope.Startup);
|
||||||
debug('Checking config JSON schema', 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 schemaValidator = new Validator();
|
||||||
const validate = schemaValidator.validate(mergedConfig, Defaults.configSchema);
|
const validate = schemaValidator.validate(mergedConfig, Defaults.configSchema);
|
||||||
|
|
||||||
if (validate.valid) {
|
if(validate.valid) {
|
||||||
debug('Config matches JSON schema', ELoggingScope.Startup);
|
debug('Config matches JSON schema', ELoggingScope.Startup);
|
||||||
} else {
|
} else {
|
||||||
// Manually send fatal message in case someone messes up the logLevel config
|
// Manually send fatal message in case someone messes up the logLevel config
|
||||||
|
@ -53,7 +59,7 @@ if (validate.valid) {
|
||||||
}
|
}
|
||||||
|
|
||||||
figlet('Argon Bot', (err, data) => {
|
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);
|
info(gradient.rainbow.multiline(`\n${data}`), ELoggingScope.Startup);
|
||||||
});
|
});
|
||||||
|
@ -62,11 +68,50 @@ const client = new Client({
|
||||||
intents: ['GUILDS', 'GUILD_MESSAGES'],
|
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 Servers: ${client.guilds.cache.size}`, ELoggingScope.Startup);
|
||||||
debug(`Total number of Users: ${client.users.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) => {
|
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',
|
logLevel: 'info',
|
||||||
},
|
},
|
||||||
configSchema: {
|
configSchema: {
|
||||||
$id: 'http://example.com/example.json',
|
required: ['token', 'logLevel', 'prefix'],
|
||||||
$schema: 'http://json-schema.org/draft-07/schema',
|
|
||||||
required: ['token', 'logLevel'],
|
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
token: {
|
token: {
|
||||||
|
@ -40,6 +38,10 @@ export const Defaults = {
|
||||||
'fatal',
|
'fatal',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
prefix: {
|
||||||
|
$id: '#/properties/prefix',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
},
|
},
|
||||||
|
|
|
@ -70,8 +70,8 @@ export function verbose(message: string, scope?: ELoggingScope): void {
|
||||||
const splitMultiline = message.split('\n');
|
const splitMultiline = message.split('\n');
|
||||||
|
|
||||||
splitMultiline.forEach((val) => {
|
splitMultiline.forEach((val) => {
|
||||||
if (verboseLevel)
|
if(verboseLevel)
|
||||||
if (scope)
|
if(scope)
|
||||||
console.log(chalk`{grey (${date})} {magenta.bold ${scope}} {white.bold [VERBOSE]}: {white ${val}}`);
|
console.log(chalk`{grey (${date})} {magenta.bold ${scope}} {white.bold [VERBOSE]}: {white ${val}}`);
|
||||||
else
|
else
|
||||||
console.log(chalk`{grey (${date})} {white.bold [VERBOSE]}: {white ${val}}`);
|
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');
|
const splitMultiline = message.split('\n');
|
||||||
|
|
||||||
splitMultiline.forEach((val) => {
|
splitMultiline.forEach((val) => {
|
||||||
if (debugLevel)
|
if(debugLevel)
|
||||||
if (scope)
|
if(scope)
|
||||||
console.log(chalk`{grey (${date})} {magenta.bold ${scope}} {blue.bold [DEBUG]}: {blue ${val}}`);
|
console.log(chalk`{grey (${date})} {magenta.bold ${scope}} {blue.bold [DEBUG]}: {blue ${val}}`);
|
||||||
else
|
else
|
||||||
console.log(chalk`{grey (${date})} {blue.bold [DEBUG]}: {blue ${val}}`);
|
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');
|
const splitMultiline = message.split('\n');
|
||||||
|
|
||||||
splitMultiline.forEach((val) => {
|
splitMultiline.forEach((val) => {
|
||||||
if (infoLevel)
|
if(infoLevel)
|
||||||
if(scope)
|
if(scope)
|
||||||
console.log(chalk`{grey (${date})} {magenta.bold ${scope}} {green.bold [INFO]}: {green ${val}}`);
|
console.log(chalk`{grey (${date})} {magenta.bold ${scope}} {green.bold [INFO]}: {green ${val}}`);
|
||||||
else
|
else
|
||||||
|
@ -109,8 +109,8 @@ export function warn(message: string, scope?: ELoggingScope): void {
|
||||||
const splitMultiline = message.split('\n');
|
const splitMultiline = message.split('\n');
|
||||||
|
|
||||||
splitMultiline.forEach((val) => {
|
splitMultiline.forEach((val) => {
|
||||||
if (warnLevel)
|
if(warnLevel)
|
||||||
if (scope)
|
if(scope)
|
||||||
console.log(chalk`{grey (${date})} {magenta.bold ${scope}} {yellow.bold [WARN]}: {yellow ${val}}`);
|
console.log(chalk`{grey (${date})} {magenta.bold ${scope}} {yellow.bold [WARN]}: {yellow ${val}}`);
|
||||||
else
|
else
|
||||||
console.log(chalk`{grey (${date})} {yellow.bold [WARN]}: {yellow ${val}}`);
|
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');
|
const splitMultiline = message.split('\n');
|
||||||
|
|
||||||
splitMultiline.forEach((val) => {
|
splitMultiline.forEach((val) => {
|
||||||
if (errorLevel)
|
if(errorLevel)
|
||||||
if (scope)
|
if(scope)
|
||||||
console.log(chalk`{grey (${date})} {magenta.bold ${scope}} {bold.underline.rgb(255, 165, 0) [ERROR]}: {underline.rgb(255, 165, 0) ${val}}`);
|
console.log(chalk`{grey (${date})} {magenta.bold ${scope}} {bold.underline.rgb(255, 165, 0) [ERROR]}: {underline.rgb(255, 165, 0) ${val}}`);
|
||||||
else
|
else
|
||||||
console.log(chalk`{grey (${date})} {bold.underline.rgb(255, 165, 0) [ERROR]}: {underline.rgb(255, 165, 0) ${val}}`);
|
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');
|
const splitMultiline = message.split('\n');
|
||||||
|
|
||||||
splitMultiline.forEach((val) => {
|
splitMultiline.forEach((val) => {
|
||||||
if (fatalLevel)
|
if(fatalLevel)
|
||||||
if (scope)
|
if(scope)
|
||||||
console.log(chalk`{grey (${date})} {magenta.bold ${scope}} {red.bold.underline [FATAL]}: {red.underline ${val}}`);
|
console.log(chalk`{grey (${date})} {magenta.bold ${scope}} {red.bold.underline [FATAL]}: {red.underline ${val}}`);
|
||||||
else
|
else
|
||||||
console.log(chalk`{grey (${date})} {red.bold.underline [FATAL]}: {red.underline ${val}}`);
|
console.log(chalk`{grey (${date})} {red.bold.underline [FATAL]}: {red.underline ${val}}`);
|
||||||
|
|
|
@ -23,5 +23,10 @@ export enum ELoggingScope {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interfaces
|
// Interfaces
|
||||||
|
export interface ICommandOptions {
|
||||||
|
name: string;
|
||||||
|
shortDescription: string;
|
||||||
|
extendedDescription: string;
|
||||||
|
}
|
||||||
|
|
||||||
// Type Aliases
|
// 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/*": [
|
"@utils/*": [
|
||||||
"src/lib/utils/*"
|
"src/lib/utils/*"
|
||||||
|
],
|
||||||
|
"@structures/*": [
|
||||||
|
"src/lib/structures/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Reference in a new issue