command handler changes

This commit is contained in:
nolan 2021-08-25 13:15:15 -07:00
parent 943da83993
commit a3faf1af13
20 changed files with 255 additions and 132 deletions

View File

@ -9,6 +9,7 @@
"GUILD_MESSAGES"
]
},
"invite": "https://discord.gg/49u6cHu",
"shardOptions": {
"totalShards": "auto"
},

View File

@ -26,6 +26,7 @@
"chalk": "^4.1.2",
"discord.js": "^13.2.0-dev.1629115738.9a833b1",
"dotenv": "^10.0.0",
"escape-string-regexp": "^5.0.0",
"eslint": "^7.32.0",
"moment": "^2.29.1",
"mongodb": "^3.5.9",

View File

@ -104,6 +104,16 @@ class Util {
return ['.', '+', '*', '?', '\\[', '\\]', '^', '$', '(', ')', '{', '}', '|', '\\\\', '-'];
}
static escapeRegex(string) {
if(typeof string !== 'string') {
throw new Error("Invalid type sent to escapeRegex.");
}
return string
.replace(/[|\\{}()[\]^$+*?.]/gu, '\\$&')
.replace(/-/gu, '\\x2d');
}
static duration(seconds) {
const { plural } = this;
let s = 0,
@ -129,7 +139,7 @@ class Util {
}
static get date() {
return moment().format("YYYY-MM-DD hh:mm:ss");
return moment().format("YYYY-MM-DD HH:mm:ss");
}
}

View File

@ -1,4 +1,4 @@
module.exports = {
Commands: require('./Commands.json'),
Emojis: require('./Emojis.json')
};
}

View File

@ -1,3 +1,30 @@
[O_COMMANDHANDLER_COMMANDNOTSYNCED]
It appears as if the command does not exist on the client.
This is an issue that should be reported to a bot developer.
[O_COMMANDHANDLER_GUILDONLY]
This command can only be run in servers.
[O_COMMANDHANDLER_TYPEINTEGER]
The command option {option} requires an integer between `{min}` and `{max}`.
[O_COMMANDHANDLER_TYPEMEMBER]
The command option {option} requires a server member.
[O_COMMANDHANDLER_TYPETEXT_CHANNEL]
The command option {option} requires a text channel.
[O_COMMANDHANDLER_TYPEVOICE_CHANNEL]
The command option {option} requires a voice channel.
[O_COMMANDHANDLER_TYPENUMBER]
The command option {option} requires a number between `{min}` and `{max}`.
[O_COMMANDHANDLER_TYPEFLOAT]
The command option {option} requires a float between `{min}` and `{max}`.
[O_COMMANDHANDLER_ERROR]
An error occured while executing that command.
It is recommended to contact a developer about this issue.
You can join the support server by clicking on the button below.

View File

@ -28,13 +28,13 @@ class BaseClient extends EventEmitter {
async build() {
await this.shardingManager.spawn().catch((err) => {
this.error(`Fatal error during shard spawning:\n${err.stack}`);
await this.shardingManager.spawn().catch((error) => {
this.logger.error(`Fatal error during shard spawning:\n${error.stack || error}`);
// eslint-disable-next-line no-process-exit
process.exit(); // Prevent a boot loop when shards die due to an error in the client
});
const API = await import('./api/index.js').catch((err) => this.warn(`Error importing API files:\n${err.stack}`));
const API = await import('./api/index.js').catch((error) => this.logger.warn(`Error importing API files:\n${error.stack || error}`));
if (API) {
this.info('Booting up API');
const { default: APIManager } = API;
@ -135,7 +135,7 @@ class BaseClient extends EventEmitter {
}
async getLiveGuild(shard, message) {
async getLiveGuild(shard, message) { //eslint-disable-line no-unused-vars
// TODO: Figure out what exactly this should return, waiting for further client implementation
// const result = await this.shardingManager.broadcastEval((client) => {
@ -144,18 +144,6 @@ class BaseClient extends EventEmitter {
}
warn(message) {
this.logger.write('warn', message);
}
info(message) {
this.logger.write('info', message);
}
error(message) {
this.logger.write('error', message);
}
}
module.exports = BaseClient;

View File

@ -4,6 +4,7 @@ const path = require('path');
const fs = require('fs');
const Util = require('../Util.js');
const { runInThisContext } = require('vm');
const Constants = {
Types: [
@ -105,6 +106,26 @@ class Logger {
return `${id}`.length === 1 ? `0${id}` : `${id}`;
}
error(message) {
this.write('error', message);
}
warn(message) {
this.write('warn', message);
}
debug(message) {
this.write('debug', message);
}
info(message) {
this.write('info', message);
}
status(message) {
this.write('status', message);
}
}
module.exports = Logger;

View File

@ -24,16 +24,17 @@ class DiscordClient extends Client {
this.resolver = new Resolver(this);
this._activity = 0;
this._options = options;
this._built = false;
this.once('ready', () => {
this._setActivity();
// this.once('ready', () => {
// this._setActivity();
setInterval(() => {
this._setActivity();
}, 1800000); // I think this is 30 minutes. I could be wrong.
});
// setInterval(() => {
// this._setActivity();
// }, 1800000); // I think this is 30 minutes. I could be wrong.
// });
}
@ -87,7 +88,6 @@ class DiscordClient extends Client {
return Boolean(this.shard.ids[0] === 0);
}
}
module.exports = DiscordClient;

View File

@ -23,6 +23,27 @@ class LocaleLoader {
}
format(language, index, parameters = {}, code = false) {
let string = this.languages[language][index];
if(!string) return `< Missing Locale: ${language}.${index} >`;
for(const [ parameter, value ] of Object.entries(parameters)) {
string = string.replace(new RegExp(`{${Util.escapeRegex(parameter.toLowerCase())}}`, 'giu'), value);
}
if(code) {
try {
string = eval(string); //eslint-disable-line no-eval
} catch(error) {
this.client.logger.error(`Locale [${language}.${index}] failed to execute code.\n${error.stack || error}`);
}
}
return string;
}
_loadLanguage(root, language) {
const directory = path.join(root, language);

View File

@ -0,0 +1,44 @@
const { SlashCommand, CommandOption } = require("../../../interfaces");
class SettingsCommand extends SlashCommand {
constructor(client) {
super(client, {
name: 'settings',
description: "Invoke to display a settings menu.",
module: 'administration',
options: [
new CommandOption({
name: 'category',
description: "Select a category to view settings for.",
type: 'STRING',
choices: [
{
name: 'Administration',
value: 'administrator'
},
{
name: 'Moderation',
value: 'moderation'
},
{
name: 'Utility',
value: 'utility'
}
],
required: true
})
],
guildOnly: true
});
}
async execute(thing, options) {
}
}
module.exports = SettingsCommand;

View File

@ -33,10 +33,12 @@ class MuteCommand extends SlashCommand {
});
}
async execute(interaction) {
async execute(thing, options) {
console.log(options);
// console.log(interaction, interaction.options);
interaction.reply(this.resolveable);
thing.reply({ content: this.resolveable });
}

View File

@ -45,11 +45,29 @@ class CommandHandler extends Observer {
const command = this._matchCommand(interaction.commandName);
const thing = new Thing(this.client, command, interaction);
if(!command) return thing.reply({ locale: 'O_COMMANDHANDLER_COMMANDNOTSYNCED', emoji: 'failure', ephemeral: true });
if(!command) return thing.reply({ content: thing.format('O_COMMANDHANDLER_COMMANDNOTSYNCED'), emoji: 'failure', ephemeral: true });
const response = await this._parseInteraction(thing);
if(response.error) {
return thing.reply({
content: thing.format(`O_COMMANDHANDLER_TYPE${response.option.type}`, { option: response.option.name, min: response.option.minimum, max: response.option.maximum }),
emoji: 'failure',
ephemeral: true
});
}
return this._executeCommand(thing, response.options);
}
async _executeCommand(thing, options) {
try {
const resolved = thing.command.execute(thing, options);
if(resolved instanceof Promise) await resolved;
} catch(error) {
this.client.logger.error(error.stack || error);
this._generateError(thing);
}
}
@ -63,9 +81,12 @@ class CommandHandler extends Observer {
}
let error = null;
const options = {};
for(const option of interaction.options._hoistedOptions) {
const matched = command.options.find((o) => o.name === option.name);
const newOption = new CommandOption({ name: matched.name, type: matched.type, _rawValue: option.value });
console.log('matched', matched)
const newOption = new CommandOption({ name: matched.name, type: matched.type, minimum: matched.minimum, maximum: matched.maximum, _rawValue: option.value });
const parsed = await this._parseOption(thing, newOption);
if(parsed.error) {
@ -77,43 +98,43 @@ class CommandHandler extends Observer {
}
newOption.value = parsed.value;
thing.options.push(newOption);
options[matched.name] = newOption;
}
if(error) return error;
return {
error: false,
//uhhh..
}
console.log(options);
options
};
}
async _parseOption(thing, option) {
const types = {
ROLES: (string) => {
// ROLES: (string) => {
},
MEMBERS: (string) => {
// },
// MEMBERS: (string) => {
},
USERS: (string) => {
// },
// USERS: (string) => {
},
CHANNELS: (string) => {
// },
// CHANNELS: (string) => {
},
TEXT_CHANNELS: (string) => {
// },
// TEXT_CHANNELS: (string) => {
},
VOICE_CHANNELS: (string) => {
// },
// VOICE_CHANNELS: (string) => {
},
// },
STRING: (string) => {
return { error: false, value: string };
},
INTEGER: (integer) => {
console.log(option);
if(option.minimum !== undefined && integer < option.minimum) return { error: true };
if(option.maximum !== undefined && integer > option.maximum) return { error: true };
return { error: false, value: parseInt(integer) };
@ -188,7 +209,26 @@ class CommandHandler extends Observer {
return command || null;
}
_generateError() {
_generateError(thing) {
thing.reply({
content: thing.format('O_COMMANDHANDLER_ERROR'),
emoji: 'failure',
ephemeral: true,
components: [
{
type: 'ACTION_ROW',
components: [
{
label: 'Support',
type: 'BUTTON',
style: 'LINK',
url: this.client._options.discord.invite
}
]
}
]
});
}

View File

@ -0,0 +1,18 @@
const { Setting } = require('../../../interfaces/');
class MuteSetting extends Setting {
constructor(client) {
super(client, {
name: 'mute',
description: 'uhhhhh'
});
}
}
module.exports = MuteSetting;

View File

@ -1,63 +0,0 @@
const Constants = {
ArgumentOptionTypes: { //https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-type
"SUB_COMMAND": 1,
"SUB_COMMAND_GROUP": 2,
"STRING": 3,
"INTEGER": 4,
"BOOLEAN": 5,
"USER": 6,
"CHANNEL": 7,
"ROLE": 8,
"MENTIONABLE": 9,
"NUMBER": 10
}
};
/*
ApplicationCommandOption {
type
name
description
required
choices
options (only used for subcommands/groups)
}
*/
class Argument {
constructor(client, options = {}) {
if(!options) return null;
this.client = client;
this.key = options.key?.toLowerCase();
this.description = options.description;
this.aliases = options.aliases || null;
this.type = options.type || 'STRING';
this.aliases = options.aliases || [];
this.types = options.types || ['FLAG', 'VERBAL'];
this.choices = options.choices || [];
this.required = Boolean(options.required);
this.infinite = Boolean(options.infinite);
this.parser = typeof options.parser === 'function' ? options.parser : null;
this.value = undefined; //Defined in the Command Handler.
}
get structure() {
return {
name: this.key,
description: this.description,
type:
}
}
}

View File

@ -35,8 +35,8 @@ class CommandOption {
this.choices = options.choices || []; //Used for STRING/INTEGER/NUMBER types.
this.options = options.options || []; //Used for SUB_COMMAND/SUB_COMMAND_GROUP types.
this.minimum = options.minimum || undefined; //Used for INTEGER/NUMBER/FLOAT types.
this.maxiumum = options.maximum || undefined;
this.minimum = typeof options.minimum === 'number' ? options.minimum : undefined; //Used for INTEGER/NUMBER/FLOAT types.
this.maximum = typeof options.maximum === 'number' ? options.maximum : undefined;
this.value = undefined;

View File

@ -18,18 +18,6 @@ class Observer extends Component {
}
execute() {
return this._continue();
}
_continue() {
return { error: false, observer: this };
}
_stop() {
return { error: true, observer: this };
}
}
module.exports = Observer;

View File

@ -0,0 +1,24 @@
const Component = require("./Component.js");
class Setting extends Component {
constructor(client, options = {}) {
if(!options) return null;
super(client, {
id: options.name,
_type: 'setting',
disabled: options.disabled,
guarded: options.guarded
});
this.name = options.name;
this.module = options.module;
this.description = options.description || "";
}
}
module.exports = Setting;

View File

@ -19,11 +19,6 @@ class Thing {
async reply(options = {}) {
if(options.locale) {
options.content = this.format(options.locale);
// delete options.locale;
}
if(options.emoji) {
if(!Emojis[options.emoji]) this.client.logger.warn(`Invalid emoji provided to command ${this.command.resolveable}: "${options.emoji}"`);
options.content = `${Emojis[options.emoji]} ${options.content}`;
@ -34,10 +29,10 @@ class Thing {
return this._pending;
}
format(locale) {
format(locale, parameters = {}, code = false) {
const language = 'en_us'; //Default language.
//TODO: Fetch guild/user settings and switch localization.
return this.client.localeLoader.languages[language][locale];
return this.client.localeLoader.format(language, locale, parameters, code);
}
get guild() {

View File

@ -5,5 +5,6 @@ module.exports = {
SlashCommand: require('./commands/SlashCommand.js'),
Command: require('./commands/Command.js'),
CommandOption: require('./CommandOption.js'),
Thing: require('./Thing.js')
Thing: require('./Thing.js'),
Setting: require('./Setting.js')
};

View File

@ -679,6 +679,11 @@ escape-string-regexp@^4.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
escape-string-regexp@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
eslint-scope@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"