@wilcosp/rex
Version:
Rex is an automated command manager for discord js
454 lines (453 loc) • 20.3 kB
JavaScript
"use strict";
/*!
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.importer = exports.RexCommandManager = void 0;
const v10_1 = require("discord-api-types/v10");
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = require("path");
const applicationCommandBase_js_1 = require("./applicationCommandBase.js");
const button_js_1 = require("./components/button.js");
const mentionSelectMenu_js_1 = require("./components/mentionSelectMenu.js");
const roleSelectMenu_js_1 = require("./components/roleSelectMenu.js");
const selectMenuBase_js_1 = require("./components/selectMenuBase.js");
const stringSelectMenu_js_1 = require("./components/stringSelectMenu.js");
const userSelectMenu_js_1 = require("./components/userSelectMenu.js");
const contextMenuCommand_js_1 = require("./contextmenu/contextMenuCommand.js");
const asyncWait_js_1 = require("./helpers/asyncWait.js");
const modalExecute_js_1 = require("./modals/modalExecute.js");
const commandBase_js_1 = require("./slashCommands/commandBase.js");
const group_js_1 = require("./slashCommands/group.js");
const sub_js_1 = require("./slashCommands/sub.js");
const types_js_1 = require("./types/types.js");
var commandTypes;
(function (commandTypes) {
commandTypes["slash"] = "slash";
})(commandTypes || (commandTypes = {}));
const RELOAD_REGEX = /(\/)?rex-?reload/i;
class RexCommandManager {
constructor(client, config) {
this.client = client;
this.config = config;
this.commands = new Map();
this.components = new Map();
this.modals = new Map();
this.initializing = true;
this.registerQueue = new asyncWait_js_1.RexAsyncDefferedQueue();
this.client.once("ready", () => {
this.logger("loading commands");
this.initializeCommands();
process.stdin.on("data", data => {
if (RELOAD_REGEX.test(data.toString("utf8"))) {
console.log("reloading all commands");
this.initializeCommands();
}
});
});
this.client.on("interactionCreate", async (inter) => {
try {
switch (inter.type) {
case v10_1.InteractionType.ApplicationCommandAutocomplete: {
this.handleAutoComplete(inter);
return;
}
case v10_1.InteractionType.ApplicationCommand: {
switch (inter.commandType) {
case v10_1.ApplicationCommandType.ChatInput: {
this.handleSlashCommand(inter);
return;
}
case v10_1.ApplicationCommandType.User:
case v10_1.ApplicationCommandType.Message: {
this.handleContextMenuCommand(inter);
return;
}
}
throw new TypeError("Type of interaction not supported", { cause: inter });
}
case v10_1.InteractionType.MessageComponent: {
this.handleComponent(inter);
return;
}
case v10_1.InteractionType.ModalSubmit: {
this.handleModal(inter);
return;
}
default:
throw new TypeError("Type of interaction not supported", { cause: inter });
}
}
catch (e) {
this.emitError(e);
}
});
}
errorHandler(error) {
throw error;
}
emitError(error) {
this.errorHandler.apply(null, [error]);
}
onError(fun) {
this.errorHandler = fun;
}
async initializeCommands() {
this.initializing = true;
this.commands.clear();
this.components.clear();
this.fetchedCommands = this.getFetchedCommands();
this.registerQueue.restart();
const promises = [this.loadCommands(this.config.slashCommandsDir), this.loadCommands(this.config.contextMenuDir)];
Promise.all(promises)
.then(() => this.unregisterCommands())
.catch(e => this.emitError(e))
.finally(() => {
this.initializing = false;
this.registerQueue.end();
});
this.loadComponents()?.catch(e => this.emitError(e));
this.loadModals()?.catch(e => this.emitError(e));
}
async fetchGuilds() {
let fetched;
const guilds = [];
let i = 1;
do {
fetched = await this.client.guilds.fetch({
after: guilds[guilds.length - 1],
});
guilds.push(...fetched.map(guild => guild.id));
} while (fetched.size > 199);
return guilds;
}
async getFetchedCommands() {
const fetchingGuilds = this.fetchGuilds();
this.DSCommandManager ?? (this.DSCommandManager = this.DSCommandManager = this.client.application?.commands);
if (!this.DSCommandManager) {
throw Error("discord js command manager isn't present");
}
const fetchedCommands = await this.DSCommandManager.fetch({
force: true,
withLocalizations: true,
}).then(fetched => {
const sorted = new Map();
mapping: for (const [, fetchCom] of fetched) {
const type = types_js_1.RexCommandMappingByNumber[fetchCom.type];
if (fetchCom.guildId) {
sorted.set(`${fetchCom.name}.${type}.${fetchCom.guildId}`, fetchCom);
this.logger("fetched command", `${fetchCom.name}.${type}.${fetchCom.guildId}`);
continue mapping;
}
sorted.set(`${fetchCom.name}.${type}.global`, fetchCom);
this.logger("fetched command", `${fetchCom.name}.${type}.global`);
continue mapping;
}
return sorted;
});
for (const guild of await fetchingGuilds) {
await this.DSCommandManager.fetch({
guildId: guild,
force: true,
withLocalizations: true,
})
.then(fetched => {
for (const [, fetchCom] of fetched) {
const type = types_js_1.RexCommandMappingByNumber[fetchCom.type];
fetchedCommands.set(`${fetchCom.name}.${type}.${fetchCom.guildId}`, fetchCom);
this.logger("fetched command", `${fetchCom.name}.${type}.${fetchCom.guildId}`);
}
})
.catch(e => this.emitError(e));
}
return fetchedCommands;
}
async loadCommands(dir) {
if (!dir)
return;
const directory = (0, path_1.join)((0, path_1.dirname)(process.argv[1]), dir);
return promises_1.default
.readdir(directory)
.then(files => files.filter(file => file.endsWith(".js") || file.endsWith(".mjs") || file.endsWith("cjs")))
.then(async (files) => {
const promises = [];
for (const file of files) {
const filepath = (0, path_1.join)(directory, file);
promises.push(promises_1.default
.stat(filepath)
.then(stats => importer(`${filepath}?t=${Math.floor(stats.ctimeMs)}`))
.then(com => {
if (com instanceof applicationCommandBase_js_1.RexApplicationCommandBase) {
return com;
}
if (com.default instanceof applicationCommandBase_js_1.RexApplicationCommandBase) {
return com.default;
}
return null;
})
.then(async (com) => {
if (!com) {
return;
}
if (com instanceof group_js_1.RexSlashCommandGroup || com instanceof sub_js_1.RexSlashCommandSub) {
await com.load(directory);
}
if (com.guilds && com.guilds?.length > 0) {
for (const guild of com.guilds) {
await this.registerCommand(com, guild);
}
return;
}
return this.registerCommand(com, "global");
})
.catch(e => this.emitError(e)));
}
return Promise.all(promises);
})
.catch(e => this.emitError(e));
}
async registerCommand(rexCom, to) {
const fetched = await this.fetchedCommands;
const type = types_js_1.RexCommandMapping[rexCom.type];
const fetchedCommand = fetched?.get(`${rexCom.name}.${type}.${to}`);
if (fetchedCommand && rexCom.equalToCommand(fetchedCommand)) {
this.commands.set(`${rexCom.name}.${type}.${to}`, rexCom);
fetched.delete(`${rexCom.name}.${type}.${to}`);
rexCom.setCommandId(fetchedCommand.id);
this.logger("loaded command", `${rexCom.name}.${type}.${to}`);
return;
}
return this.registerQueue
.wait()
.then(() => this.logger("going to register command", `${rexCom.name}.${type}.${to}`))
.then(() => this.DSCommandManager?.create(rexCom.toCommand(), to == "global" ? undefined : to)?.then(r => {
this.commands.set(`${rexCom.name}.${type}.${to}`, rexCom);
fetched.delete(`${rexCom.name}.${type}.${to}`);
rexCom.setCommandId(r.id);
this.logger("registered command", `${rexCom.name}.${type}.${to}`);
}))
.catch(e => this.emitError(e))
.finally(() => this.registerQueue.next());
}
handleSlashCommand(inter) {
const appCom = inter.command;
const rexCom = this.commands.get(`${appCom?.name}.${types_js_1.RexCommandMapping.ChatInput}.${appCom?.guildId ?? "global"}`);
if (rexCom instanceof commandBase_js_1.RexSlashCommandBase) {
if (!rexCom) {
if (this.initializing) {
return;
}
return inter.reply({
content: "command not found",
ephemeral: true,
});
}
return rexCom.run(inter, this.commands);
}
}
handleAutoComplete(inter) {
const appCom = inter.command;
const rexCom = this.commands.get(`${appCom?.name}.${types_js_1.RexCommandMapping.ChatInput}.${appCom?.guildId ?? "global"}`);
if (rexCom instanceof commandBase_js_1.RexSlashCommandBase) {
if (!rexCom) {
return;
}
return rexCom.runAutoComplete(inter, new Map(this.commands));
}
}
handleContextMenuCommand(inter) {
const appCom = inter.command;
if (!appCom) {
return inter.reply({
ephemeral: true,
content: "command couldn't be found",
});
}
const type = types_js_1.RexCommandMappingByNumber[appCom.type];
const rexCom = this.commands.get(`${appCom?.name}.${type}.${appCom.guildId ?? "global"}`);
if (!rexCom || !(rexCom instanceof contextMenuCommand_js_1.RexContextMenuCommand)) {
return inter.reply({
ephemeral: true,
content: "can't find the command",
});
}
return rexCom.run(inter);
}
async unregisterCommands() {
await this.registerQueue.wait();
this.registerQueue.next();
if (this.registerQueue.size > 0) {
return this.unregisterCommands();
}
const fetched = await this.fetchedCommands;
if (!fetched || fetched.size < 1)
return;
for (const [, fetchCommand] of fetched) {
await fetchCommand.delete();
this.logger("unregistered command", `${fetchCommand.name}.${types_js_1.RexCommandMappingByNumber[fetchCommand.type]}.${fetchCommand.guildId ?? "global"}`);
}
this.logger("done with unregistering commands");
}
loadComponents() {
if (!this.config.componentsDir)
return;
const directory = (0, path_1.join)((0, path_1.dirname)(process.argv[1]), this.config.componentsDir);
return promises_1.default
.readdir(directory)
.then(files => files.filter(file => file.endsWith(".js") || file.endsWith(".mjs") || file.endsWith("cjs")))
.then(async (files) => {
for (const file of files) {
const filepath = (0, path_1.join)(directory, file);
promises_1.default.stat(filepath)
.then(stats => importer(`${filepath}?t=${stats.ctimeMs}`))
.then(com => {
if (com instanceof stringSelectMenu_js_1.RexStringSelectMenuComponent || com instanceof button_js_1.RexButtonComponent || com instanceof selectMenuBase_js_1.RexSelectMenuBase) {
return com;
}
return com.default;
})
.then(com => {
if (com instanceof stringSelectMenu_js_1.RexStringSelectMenuComponent) {
this.components.set(`${com.customId}.${types_js_1.RexComponentMapping.string}`, com);
this.logger(`loaded string select menu ${com.customId}`);
return;
}
if (com instanceof button_js_1.RexButtonComponent) {
this.components.set(`${com.customId}.${types_js_1.RexComponentMapping.button}`, com);
this.logger(`loaded button ${com.customId}`);
return;
}
if (com instanceof roleSelectMenu_js_1.RexRoleSelectComponent) {
this.components.set(`${com.customId}.${types_js_1.RexComponentMapping.role}`, com);
this.logger(`loaded role select menu ${com.customId}`);
return;
}
if (com instanceof userSelectMenu_js_1.RexUserSelectMenuComponent) {
this.components.set(`${com.customId}.${types_js_1.RexComponentMapping.user}`, com);
this.logger(`loaded user select menu ${com.customId}`);
return;
}
if (com instanceof mentionSelectMenu_js_1.RexMentionSelectMenuComponent) {
this.components.set(`${com.customId}.${types_js_1.RexComponentMapping.mention}`, com);
this.logger(`loaded mention select menu ${com.customId}`);
return;
}
})
.catch(e => this.emitError(e));
}
})
.catch(e => this.emitError(e));
}
handleComponent(inter) {
if (inter.isButton()) {
const rexComp = this.components.get(`${inter.customId}.${types_js_1.RexComponentMapping.button}`);
if (rexComp instanceof button_js_1.RexButtonComponent) {
return rexComp.run(inter);
}
}
if (inter.isStringSelectMenu()) {
const rexComp = this.components.get(`${inter.customId}.${types_js_1.RexComponentMapping.string}`);
if (rexComp instanceof stringSelectMenu_js_1.RexStringSelectMenuComponent) {
return rexComp.run(inter);
}
}
if (inter.isRoleSelectMenu()) {
const rexComp = this.components.get(`${inter.customId}.${types_js_1.RexComponentMapping.role}`);
if (rexComp instanceof roleSelectMenu_js_1.RexRoleSelectComponent) {
return rexComp.run(inter);
}
}
if (inter.isUserSelectMenu()) {
const rexComp = this.components.get(`${inter.customId}.${types_js_1.RexComponentMapping.user}`);
if (rexComp instanceof userSelectMenu_js_1.RexUserSelectMenuComponent) {
return rexComp.run(inter);
}
}
if (inter.isMentionableSelectMenu()) {
const rexComp = this.components.get(`${inter.customId}.${types_js_1.RexComponentMapping.mention}`);
if (rexComp instanceof mentionSelectMenu_js_1.RexMentionSelectMenuComponent) {
return rexComp.run(inter);
}
}
}
loadModals() {
if (!this.config.modalDir)
return;
const directory = (0, path_1.join)((0, path_1.dirname)(process.argv[1]), this.config.modalDir);
return promises_1.default
.readdir(directory)
.then(files => files.filter(file => file.endsWith(".js") || file.endsWith(".mjs") || file.endsWith("cjs")))
.then(async (files) => {
for (const file of files) {
const filepath = (0, path_1.join)(directory, file);
promises_1.default.stat(filepath)
.then(stats => importer(`${filepath}?t=${stats.ctimeMs}`))
.then(com => {
if (com instanceof modalExecute_js_1.RexModalExecute) {
return com;
}
return com.default;
})
.then(com => {
if (com instanceof modalExecute_js_1.RexModalExecute) {
this.logger(`loaded modal ${com.customId}`);
this.modals.set(`${com.customId}.modal`, com);
return;
}
})
.catch(e => this.emitError(e));
}
})
.catch(e => this.emitError(e));
}
handleModal(inter) {
const RexModal = this.modals.get(`${inter.customId}.modal`);
if (RexModal instanceof modalExecute_js_1.RexModalExecute) {
return RexModal.run(inter);
}
}
logger(...parms) {
if (this.config.log) {
console.log(...parms);
}
}
}
exports.RexCommandManager = RexCommandManager;
async function importer(file) {
if (file.endsWith(".mjs") || file.includes(".mjs?")) {
return esmImport(file);
}
return _a = file, Promise.resolve().then(() => __importStar(require(_a)));
}
exports.importer = importer;
const esmImport = new Function("file", "return import(file)");
const mdlExtensions = /\.(m|c)?js/i;