forked from Galactic/galactic-bot
imported localeloader, added some option parsing to command handler. still nowhere near completion.
This commit is contained in:
parent
1224ac96dd
commit
569a00999a
@ -4,7 +4,11 @@
|
||||
"developer": "132620781791346688",
|
||||
"clientId": "697791541690892369",
|
||||
"clientOptions": {
|
||||
|
||||
"intents": [
|
||||
"GUILD",
|
||||
"GUILD_MEMBERS",
|
||||
"GUILD_MESSAGES"
|
||||
]
|
||||
},
|
||||
"shardOptions": {
|
||||
"totalShards": "auto"
|
||||
|
2
src/localization/en_us/commands/en_us_moderation.lang
Normal file
2
src/localization/en_us/commands/en_us_moderation.lang
Normal file
@ -0,0 +1,2 @@
|
||||
//Mute Command
|
||||
[C_MUTE_DESCRIPTION]
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"developer:test": {
|
||||
"description": "A test command used for testing purposes.",
|
||||
"responses": {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
[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.
|
@ -16,17 +16,17 @@ class SlashCommandManager {
|
||||
if(message.type === 'global') {
|
||||
await this.global(message.commands);
|
||||
} else if(message.type === 'guild') {
|
||||
await this.guild(message.commands, { guilds: message.guilds });
|
||||
await this.guild(message.commands, { guilds: message.guilds, clientId: message.clientId });
|
||||
}
|
||||
}
|
||||
|
||||
async guild(commands, { guilds = [] }) {
|
||||
async guild(commands, { guilds = [], clientId }) {
|
||||
if(guilds.length === 0) guilds = this.client._options.discord.slashCommands.developerGuilds;
|
||||
|
||||
const promises = [];
|
||||
for(const guild of guilds) {
|
||||
promises.push(this.rest.put(
|
||||
Routes.applicationGuildCommands(this.client._options.discord.clientId, guild),
|
||||
Routes.applicationGuildCommands(clientId, guild),
|
||||
{ body: commands }
|
||||
));
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
const { Client, Intents } = require('discord.js');
|
||||
|
||||
const { Logger, Intercom, EventHooker, Registry, Dispatcher, Resolver } = require('./client/');
|
||||
const { Logger, Intercom, EventHooker, LocaleLoader, Registry, Dispatcher, Resolver } = require('./client/');
|
||||
const { Observer, Command } = require('./interfaces/');
|
||||
|
||||
const options = require('../../options.json');
|
||||
@ -11,13 +11,13 @@ class DiscordClient extends Client {
|
||||
if(!options) return null;
|
||||
|
||||
super({
|
||||
...options.discord.clientOptions,
|
||||
intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES]
|
||||
...options.discord.clientOptions
|
||||
});
|
||||
|
||||
this.eventHooker = new EventHooker(this);
|
||||
this.intercom = new Intercom(this);
|
||||
this.logger = new Logger(this);
|
||||
this.localeLoader = new LocaleLoader(this);
|
||||
|
||||
this.registry = new Registry(this);
|
||||
this.dispatcher = new Dispatcher(this);
|
||||
@ -36,6 +36,9 @@ class DiscordClient extends Client {
|
||||
const beforeTime = Date.now();
|
||||
|
||||
//Initialize components, localization, and observers.
|
||||
|
||||
await this.localeLoader.loadLanguages();
|
||||
|
||||
await this.registry.loadComponents('components/observers', Observer);
|
||||
await this.registry.loadComponents('components/commands', Command);
|
||||
|
||||
|
@ -24,9 +24,9 @@ class Intercom {
|
||||
|
||||
const commands = this.client.registry.components
|
||||
.filter((c) => c._type === 'command' && c.slash)
|
||||
.map((c) => c.json);
|
||||
.map((c) => c.shape);
|
||||
|
||||
this.send('commands', { type: 'guild', commands });
|
||||
this.send('commands', { type: 'guild', commands, clientId: this.client.application.id });
|
||||
|
||||
}
|
||||
|
||||
|
82
src/structure/client/LocaleLoader.js
Normal file
82
src/structure/client/LocaleLoader.js
Normal file
@ -0,0 +1,82 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const chalk = require('chalk');
|
||||
|
||||
const Util = require('../../Util.js');
|
||||
|
||||
class LocaleLoader {
|
||||
|
||||
constructor(client) {
|
||||
|
||||
this.client = client;
|
||||
|
||||
this.languages = {};
|
||||
|
||||
}
|
||||
|
||||
async loadLanguages() {
|
||||
|
||||
const root = path.join(process.cwd(), "src/localization");
|
||||
const directories = fs.readdirSync(root); //locale directories (en_us, fi_fi)
|
||||
|
||||
for(const directory of directories) this._loadLanguage(root, directory);
|
||||
|
||||
}
|
||||
|
||||
_loadLanguage(root, language) {
|
||||
|
||||
const directory = path.join(root, language);
|
||||
const files = Util.readdirRecursive(directory);
|
||||
|
||||
const combined = {};
|
||||
for(let file of files) {
|
||||
file = fs.readFileSync(file, {
|
||||
encoding: 'utf8'
|
||||
});
|
||||
const result = this._loadFile(file);
|
||||
Object.assign(combined, result);
|
||||
}
|
||||
|
||||
this.languages[language] = combined;
|
||||
this.client.logger.info(`Language ${chalk.bold(language)} was ${chalk.bold("loaded")}.`);
|
||||
|
||||
}
|
||||
|
||||
_loadFile(file) {
|
||||
|
||||
if(process.platform === 'win32') {
|
||||
file = file.split('\n').join('');
|
||||
file = file.replace(/\r/gu, '\n');
|
||||
}
|
||||
|
||||
const lines = file.split('\n');
|
||||
const parsed = {};
|
||||
|
||||
let matched = null,
|
||||
text = [];
|
||||
for(const line of lines) {
|
||||
if(line.startsWith('//') || line.startsWith('#')) continue;
|
||||
const matches = line.match(/\[([_A-Z0-9]{1,})\]/u);
|
||||
if(matches) {
|
||||
if (matched) {
|
||||
parsed[matched] = text.join('\n').trim();
|
||||
[, matched] = matches;
|
||||
text = [];
|
||||
} else {
|
||||
[, matched] = matches;
|
||||
}
|
||||
} else if (matched) {
|
||||
text.push(line);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
parsed[matched] = text.join('\n').trim();
|
||||
return parsed;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = LocaleLoader;
|
@ -4,5 +4,6 @@ module.exports = {
|
||||
Logger: require('./Logger.js'),
|
||||
Dispatcher: require('./Dispatcher.js'),
|
||||
Registry: require('./Registry.js'),
|
||||
Resolver: require('./Resolver.js')
|
||||
Resolver: require('./Resolver.js'),
|
||||
LocaleLoader: require('./LocaleLoader.js')
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
const { SlashCommand } = require('../../../interfaces/');
|
||||
const { SlashCommand, CommandOption } = require('../../../interfaces/');
|
||||
|
||||
class MuteCommand extends SlashCommand {
|
||||
|
||||
@ -7,12 +7,36 @@ class MuteCommand extends SlashCommand {
|
||||
name: 'mute',
|
||||
description: "Silence people.",
|
||||
module: 'moderation',
|
||||
arguments: [
|
||||
]
|
||||
options: [
|
||||
new CommandOption({
|
||||
name: 'targets',
|
||||
description: "Provide users to mute.",
|
||||
type: 'MEMBER'
|
||||
}),
|
||||
new CommandOption({
|
||||
name: 'reason',
|
||||
description: "Provide a reason.",
|
||||
type: 'STRING'
|
||||
}),
|
||||
new CommandOption({
|
||||
name: 'points',
|
||||
description: "Assign points to the infraction.",
|
||||
type: 'INTEGER',
|
||||
minimum: 0, maximum: 100
|
||||
}),
|
||||
new CommandOption({
|
||||
name: 'channel',
|
||||
type: 'TEXT_CHANNEL'
|
||||
})
|
||||
],
|
||||
guildOnly: true
|
||||
});
|
||||
}
|
||||
|
||||
async execute(thing) {
|
||||
async execute(interaction) {
|
||||
|
||||
// console.log(interaction, interaction.options);
|
||||
interaction.reply(this.resolveable);
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { Observer } = require('../../interfaces/');
|
||||
const { Observer, Thing, CommandOption } = require('../../interfaces/');
|
||||
|
||||
class CommandHandler extends Observer {
|
||||
|
||||
@ -21,8 +21,6 @@ class CommandHandler extends Observer {
|
||||
|
||||
async messageCreate(message) {
|
||||
|
||||
const { prefix } = this.client._options.discord;
|
||||
|
||||
if(!this.client._built
|
||||
|| message.webhookId
|
||||
|| message.author.bot
|
||||
@ -40,17 +38,122 @@ class CommandHandler extends Observer {
|
||||
}
|
||||
|
||||
async interactionCreate(interaction) {
|
||||
if(!interaction.isCommand()) return undefined;
|
||||
if(!interaction.isCommand()
|
||||
&& !interaction.isContextMenu()) return undefined;
|
||||
|
||||
// if(!this.client._built
|
||||
// || message.guild && !message.guild.available) return undefined;
|
||||
if(!this.client._built
|
||||
|| !interaction?.guild?.available) return undefined;
|
||||
|
||||
const command = this._matchCommand(interaction.commandName);
|
||||
console.log(interaction.commandName)
|
||||
if(!command) return interaction.reply('Command is not synced with client instance.');
|
||||
const thing = new Thing(this.client, command, interaction);
|
||||
|
||||
interaction.reply(command.resolveable);
|
||||
if(!command) return thing.reply({ locale: 'O_COMMANDHANDLER_COMMANDNOTSYNCED', emoji: 'failure', ephemeral: true });
|
||||
|
||||
const response = await this._parseInteraction(thing);
|
||||
|
||||
}
|
||||
|
||||
async _parseInteraction(thing) {
|
||||
|
||||
const { command, interaction } = thing;
|
||||
|
||||
if(!interaction.guild && command.guildOnly) {
|
||||
return thing.reply({ locale: 'O_COMMANDHANDLER_GUILDONLY', emoji: 'failure', ephemeral: true });
|
||||
}
|
||||
|
||||
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 });
|
||||
|
||||
const parsed = await this._parseOption(thing, newOption);
|
||||
if(parsed.error) {
|
||||
//uhh
|
||||
}
|
||||
|
||||
newOption.value = parsed.value;
|
||||
|
||||
options.push(newOption);
|
||||
}
|
||||
console.log(options);
|
||||
|
||||
}
|
||||
|
||||
async _parseOption(thing, option) {
|
||||
|
||||
const types = {
|
||||
ROLES: (string) => {
|
||||
|
||||
},
|
||||
MEMBERS: (string) => {
|
||||
|
||||
},
|
||||
USERS: (string) => {
|
||||
|
||||
},
|
||||
CHANNELS: (string) => {
|
||||
|
||||
},
|
||||
TEXT_CHANNELS: (string) => {
|
||||
|
||||
},
|
||||
VOICE_CHANNELS: (string) => {
|
||||
|
||||
},
|
||||
STRING: (string) => {
|
||||
return { error: false, value: string };
|
||||
},
|
||||
INTEGER: (integer) => {
|
||||
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) };
|
||||
},
|
||||
BOOLEAN: (boolean) => {
|
||||
return { error: false, value: boolean };
|
||||
},
|
||||
MEMBER: async (user) => {
|
||||
let member = null;
|
||||
try {
|
||||
member = await thing.guild.members.fetch(user);
|
||||
} catch(error) {} //eslint-disable-line no-empty
|
||||
if(!member) return { error: true };
|
||||
return { error: false, value: member };
|
||||
},
|
||||
USER: (user) => {
|
||||
return { error: false, value: user };
|
||||
},
|
||||
TEXT_CHANNEL: (channel) => {
|
||||
if(channel.type !== 'GUILD_TEXT') return { error: true };
|
||||
return { error: false, value: channel };
|
||||
},
|
||||
VOICE_CHANNEL: (channel) => {
|
||||
if(channel.type !== 'GUILD_VOICE') return { error: true };
|
||||
return { error: false, value: channel };
|
||||
},
|
||||
CHANNEL: (channel) => {
|
||||
return { error: false, value: channel };
|
||||
},
|
||||
ROLE: (role) => {
|
||||
return { error: false, value: role };
|
||||
},
|
||||
MENTIONABLE: (mentionable) => {
|
||||
return { error: false, value: mentionable };
|
||||
},
|
||||
NUMBER: (number) => {
|
||||
if(option.minimum !== undefined && number < option.minimum) return { error: true };
|
||||
if(option.maximum !== undefined && number > option.maximum) return { error: true };
|
||||
return { error: false, value: number };
|
||||
},
|
||||
FLOAT: (float) => {
|
||||
if(option.minimum !== undefined && float < option.minimum) return { error: true };
|
||||
if(option.maximum !== undefined && float > option.maximum) return { error: true };
|
||||
return { error: false, value: parseFloat(float) };
|
||||
}
|
||||
};
|
||||
|
||||
const result = types[option.type](option._rawValue);
|
||||
if(result instanceof Promise) await result;
|
||||
return result;
|
||||
}
|
||||
|
||||
async _getCommand(message) {
|
||||
@ -58,17 +161,9 @@ class CommandHandler extends Observer {
|
||||
//TODO: Move this somewhere else. RegExp should not be created every method call, but it requires the client user to be loaded.
|
||||
const mentionPattern = new RegExp(`^(<@!?${this.client.user.id}>)`, 'iu');
|
||||
|
||||
const { prefix } = this.client._options.discord;
|
||||
const start = message.content.slice(0, prefix.length);
|
||||
|
||||
let command = null,
|
||||
parameters = [];
|
||||
if(start === prefix) {
|
||||
const remaining = message.content.slice(prefix.length);
|
||||
const [ commandName, ...rest ] = remaining.split(" ");
|
||||
command = this._matchCommand(message, commandName);
|
||||
parameters = rest.join(" ");
|
||||
} else if (mentionPattern.test(message.content)) {
|
||||
if(mentionPattern.test(message.content)) {
|
||||
const [ , commandName, ...rest] = message.content.split(" ");
|
||||
command = this._matchCommand(message, commandName);
|
||||
parameters = rest.join(" ");
|
||||
@ -79,11 +174,11 @@ class CommandHandler extends Observer {
|
||||
}
|
||||
|
||||
_matchCommand(commandName) {
|
||||
|
||||
const [ command ] = this.client.resolver.components(commandName, 'command', true);
|
||||
if(!command) return null;
|
||||
|
||||
return command;
|
||||
return command || null;
|
||||
}
|
||||
|
||||
_generateError() {
|
||||
|
||||
}
|
||||
|
||||
|
60
src/structure/interfaces/CommandOption.js
Normal file
60
src/structure/interfaces/CommandOption.js
Normal file
@ -0,0 +1,60 @@
|
||||
const Constants = {
|
||||
CommandOptionTypes: {
|
||||
SUB_COMMAND: 1,
|
||||
SUB_COMMAND_GROUP: 2,
|
||||
ROLES: 3, //Note plurality, strings can parse users, roles, and channels.
|
||||
MEMBERS: 3,
|
||||
USERS: 3,
|
||||
CHANNELS: 3,
|
||||
TEXT_CHANNELS: 3,
|
||||
VOICE_CHANNELS: 3,
|
||||
STRING: 3,
|
||||
INTEGER: 4,
|
||||
BOOLEAN: 5,
|
||||
MEMBER: 6,
|
||||
USER: 6,
|
||||
TEXT_CHANNEL: 7,
|
||||
VOICE_CHANNEL: 7,
|
||||
CHANNEL: 7,
|
||||
ROLE: 8,
|
||||
MENTIONABLE: 9,
|
||||
NUMBER: 10,
|
||||
FLOAT: 10
|
||||
}
|
||||
};
|
||||
|
||||
class CommandOption {
|
||||
|
||||
constructor(options = {}) {
|
||||
|
||||
this.name = options.name;
|
||||
this.description = options.description || "A missing description, let a bot developer know.";
|
||||
|
||||
this.type = Object.keys(Constants.CommandOptionTypes).includes(options.type) ? options.type : 'STRING';
|
||||
this.required = Boolean(options.required);
|
||||
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.value = undefined;
|
||||
|
||||
this._rawValue = options._rawValue || null; //Raw value input from Discord.
|
||||
|
||||
}
|
||||
|
||||
get shape() {
|
||||
return {
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
type: Constants.CommandOptionTypes[this.type],
|
||||
required: this.required,
|
||||
choices: this.choices,
|
||||
options: this.options.map((o) => o.shape)
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = CommandOption;
|
@ -1,71 +1,74 @@
|
||||
const { Emojis } = require('../../constants');
|
||||
const { Emojis } = require('../../constants/');
|
||||
|
||||
class Thing {
|
||||
|
||||
constructor(client, options = {}) {
|
||||
if(!options) return null;
|
||||
constructor(client, command, interaction) {
|
||||
|
||||
this.message = options.message || null;
|
||||
this.interaction = options.interaction || null;
|
||||
this.client = client;
|
||||
this.interaction = interaction;
|
||||
this.command = command;
|
||||
|
||||
this.command = options.command; //Should always be provided.
|
||||
this.arguments = options.arguments;
|
||||
this.options = [];
|
||||
|
||||
this.parameters = options.parameters || [];
|
||||
|
||||
this._resolved = false;
|
||||
this._guild = null;
|
||||
this._channel = null;
|
||||
|
||||
this._pending = null;
|
||||
|
||||
}
|
||||
|
||||
async resolve() {
|
||||
async reply(options = {}) {
|
||||
|
||||
if(this.command.showUsage && !this.parameters.length && !this.arguments.length) {
|
||||
console.log('Show usage embed'); //eslint-disable-line no-console
|
||||
return undefined;
|
||||
if(options.locale) {
|
||||
options.content = this.format(options.locale);
|
||||
// delete options.locale;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = this.command.execute(this);
|
||||
if(response instanceof Promise) await response;
|
||||
this.command._invokes.successes++;
|
||||
this.client.emit('commandExecute', { instance: this, type: 'SUCCESS' });
|
||||
return { error: false };
|
||||
} catch(error) {
|
||||
this.command._invokes.failures++;
|
||||
this.client.emit('commandExecute', { instance: this, type: 'FAILURE' });
|
||||
return { error: true, message: error };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
async respond(content, options = {}) {
|
||||
if(typeof content === 'string') {
|
||||
if(options.emoji && Emojis[options.emoji]) {
|
||||
content = `${Emojis[options.emoji]} ${content}`;
|
||||
}
|
||||
if(options.reply) content = `<@${this.message.author.id}> ${content}`;
|
||||
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}`;
|
||||
// delete options.emoji;
|
||||
}
|
||||
|
||||
this._pending = this.interaction.reply(options);
|
||||
return this._pending;
|
||||
}
|
||||
|
||||
async send(options) {
|
||||
if(this.type === 'MESSAGE') {
|
||||
this.message.channel.send({
|
||||
|
||||
});
|
||||
} else {
|
||||
this.interaction.reply({
|
||||
|
||||
});
|
||||
}
|
||||
format(locale) {
|
||||
const language = 'en_us'; //Default language.
|
||||
//TODO: Fetch guild/user settings and switch localization.
|
||||
return this.client.localeLoader.languages[language][locale];
|
||||
}
|
||||
|
||||
get type() {
|
||||
if(this.message) return 'MESSAGE';
|
||||
return 'INTERACTION';
|
||||
get guild() {
|
||||
return this.interaction.guild || null;
|
||||
}
|
||||
|
||||
get channel() {
|
||||
return this.interaction.channel || null;
|
||||
}
|
||||
|
||||
// async guild() {
|
||||
// if(!this.interaction.guild) return null;
|
||||
// if(this._guild) return this._guild;
|
||||
// this._guild = await this.client.guilds.fetch(this.interaction.guildId);
|
||||
// return this._guild;
|
||||
// }
|
||||
|
||||
// async channel() {
|
||||
// if(!this.interaction.channel) return null;
|
||||
// if(this._channel) return this._channel;
|
||||
// this._channel = await this.client.channels.fetch(this.interaction.channelId);
|
||||
// return this._channel;
|
||||
// }
|
||||
|
||||
get user() {
|
||||
return this.interaction.user || null;
|
||||
}
|
||||
|
||||
get member() {
|
||||
return this.interaction.member || null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Thing;
|
@ -1,4 +1,4 @@
|
||||
const Component = require('./Component.js');
|
||||
const Component = require('../Component.js');
|
||||
|
||||
class Command extends Component {
|
||||
|
||||
@ -25,7 +25,8 @@ class Command extends Component {
|
||||
this.guildOnly = Boolean(options?.guildOnly);
|
||||
|
||||
this.archivable = options.archivable === undefined ? true : Boolean(options.archivable);
|
||||
this.slash = Boolean(options?.slash);
|
||||
|
||||
this.slash = Boolean(options.slash);
|
||||
|
||||
this._invokes = {
|
||||
success: 0,
|
8
src/structure/interfaces/commands/LegacyCommand.js
Normal file
8
src/structure/interfaces/commands/LegacyCommand.js
Normal file
@ -0,0 +1,8 @@
|
||||
const { Command } = require('./Command.js');
|
||||
|
||||
class LegacyCommand extends Command {
|
||||
|
||||
|
||||
}
|
||||
|
||||
module.exports = LegacyCommand;
|
@ -1,5 +1,5 @@
|
||||
const Command = require('./Command.js');
|
||||
const { Commands: CommandsConstant } = require('../../constants/');
|
||||
const { Commands: CommandsConstant } = require('../../../constants/');
|
||||
|
||||
class SlashCommand extends Command {
|
||||
|
||||
@ -21,16 +21,16 @@ class SlashCommand extends Command {
|
||||
|
||||
this.type = Object.keys(CommandsConstant.ApplicationCommandTypes).includes(options.type) ? options.type : 'CHAT_INPUT';
|
||||
this.options = options.options || [];
|
||||
this.defaultPermission = options.defaultPermission || {};
|
||||
this.defaultPermission = options.defaultPermission || true;
|
||||
|
||||
}
|
||||
|
||||
get json() {
|
||||
get shape() {
|
||||
return {
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
type: CommandsConstant.ApplicationCommandTypes[this.type],
|
||||
options: this.options,
|
||||
options: this.options.map((o) => o.shape),
|
||||
defaultPermission: this.defaultPermission
|
||||
};
|
||||
}
|
@ -2,7 +2,8 @@ module.exports = {
|
||||
Component: require('./Component.js'),
|
||||
Observer: require('./Observer.js'),
|
||||
Module: require('./Module.js'),
|
||||
SlashCommand: require('./SlashCommand.js'),
|
||||
Command: require('./Command.js'),
|
||||
SlashCommand: require('./commands/SlashCommand.js'),
|
||||
Command: require('./commands/Command.js'),
|
||||
CommandOption: require('./CommandOption.js'),
|
||||
Thing: require('./Thing.js')
|
||||
};
|
Loading…
Reference in New Issue
Block a user