UNPKG

djsbotbase-test

Version:

Discord.js tabanlı komut ve etkinlik sistemlerine sahip bir bot temeli

512 lines (511 loc) 19.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CommandHandler = void 0; const slashCommandClass_1 = require("./slashCommandClass"); const commandClass_1 = require("./commandClass"); const promises_1 = require("fs/promises"); const logger_1 = require("../helpers/logger"); const cooldown_1 = require("../helpers/cooldown"); class CommandHandler { constructor(data) { Object.defineProperty(this, "commandMap", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "slashCommandMap", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "commandsDir", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "slashCommandsDir", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "developerIds", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "prefix", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "suppressWarnings", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "messages", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "maintenance", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "commandRunner", { enumerable: true, configurable: true, writable: true, value: (message) => { this.runDefaultHandler(message); } }); Object.defineProperty(this, "slashCommandRunner", { enumerable: true, configurable: true, writable: true, value: (interaction) => { this.runDefaultSlashHandler(interaction); } }); const config = this.validateAndNormalizeConfig(data); this.commandsDir = config.commandsDir; this.slashCommandsDir = config.slashCommandsDir; this.developerIds = Object.freeze(config.developerIds); this.prefix = config.prefix; this.suppressWarnings = config.suppressWarnings; this.messages = config.messages; this.maintenance = config.maintenance; } validateAndNormalizeConfig(data) { const config = { commandsDir: "commands", slashCommandsDir: "slashCommands", developerIds: [], prefix: "", suppressWarnings: false, messages: {}, maintenance: false, }; if (!data) return config; if ("commandsDir" in data) { if (typeof data.commandsDir !== "string") { (0, logger_1.error)("commandsDir must be a string."); } config.commandsDir = data.commandsDir; } if ("slashCommandsDir" in data) { if (typeof data.slashCommandsDir !== "string") { (0, logger_1.error)("slashCommandsDir must be a string."); } config.slashCommandsDir = data.slashCommandsDir; } if ("developerIds" in data) { if (!Array.isArray(data.developerIds)) { (0, logger_1.error)("developerIds must be a string array."); } const invalidId = data.developerIds.find(id => typeof id !== "string"); if (invalidId !== undefined) { (0, logger_1.error)("All developer IDs must be strings."); } config.developerIds = [...data.developerIds]; } if ("prefix" in data) { if (typeof data.prefix !== "string") { (0, logger_1.error)("prefix must be a string."); } config.prefix = data.prefix; } if ("suppressWarnings" in data) { if (typeof data.suppressWarnings !== "boolean") { (0, logger_1.error)("suppressWarnings must be a boolean."); } config.suppressWarnings = data.suppressWarnings; } if ("messages" in data) { if (!data.messages || typeof data.messages !== "object" || Array.isArray(data.messages)) { (0, logger_1.error)("messages must be an object."); } this.validateMessages(data.messages); config.messages = { ...data.messages }; } if ("maintenance" in data) { if (typeof data.maintenance !== "boolean") { (0, logger_1.error)("maintenance must be a boolean."); } config.maintenance = data.maintenance; } return config; } validateMessages(messages) { const validMessageKeys = ["cooldown", "maintenance"]; for (const key of validMessageKeys) { if (key in messages) { const message = messages[key]; if (typeof message !== "string") { (0, logger_1.error)(`messages.${key} must be a string.`); } } } } async setCommands() { try { await this.loadCommandsFromDirectory(); } catch (error) { (0, logger_1.logError)("Failed to set commands!"); console.error(error); } } async loadCommandsFromDirectory() { const normalizedDir = this.normalizeDirectoryPath(this.commandsDir); const commandFiles = await this.getValidCommandFiles(normalizedDir); (0, logger_1.logInfo)("Reading commands..."); let loadedCount = 0; for (const fileName of commandFiles) { try { const command = await this.loadCommandFromFile(normalizedDir, fileName); if (command && this.validateAndAddCommand(command, fileName)) { loadedCount++; } } catch (error) { (0, logger_1.logError)(`Failed to load command '${fileName}'!`); console.error(error); } } (0, logger_1.logInfo)(`${loadedCount.toString()} ${loadedCount === 1 ? "command" : "commands"} registered.`); } async getValidCommandFiles(directory) { const dirents = await (0, promises_1.readdir)(directory, { withFileTypes: true }); return dirents .filter(dirent => dirent.isFile()) .map(dirent => dirent.name) .filter(name => this.isValidScriptFile(name, "command")); } isValidScriptFile(fileName, type) { const fileRegex = /^[\w\s]+\.(js|ts)$/; if (!fileRegex.test(fileName)) { if (!this.suppressWarnings) { (0, logger_1.logWarn)(`'${fileName}' is not a valid JavaScript/TypeScript file for ${type}.`); } return false; } return true; } async loadCommandFromFile(directory, fileName) { const commandPath = `../../../../${directory}/${fileName}`; const commandModule = await import(commandPath); const commandData = commandModule?.default; if (!commandData) { if (!this.suppressWarnings) { (0, logger_1.logWarn)(`'${fileName}' does not have a default export.`); } return null; } if (!(commandData instanceof commandClass_1.Command)) { if (!this.suppressWarnings) { (0, logger_1.logWarn)(`'${fileName}' does not export a Command instance as default.`); } return null; } return commandData; } validateAndAddCommand(command, fileName) { if (this.getCommandOrAliases(command.name)) { if (!this.suppressWarnings) { (0, logger_1.logWarn)(`Command '${command.name}' from '${fileName}' already exists.`); } return false; } const conflictingAlias = command.aliases.find(alias => this.getCommandOrAliases(alias)); if (conflictingAlias) { if (!this.suppressWarnings) { (0, logger_1.logWarn)(`Command '${command.name}' has conflicting alias '${conflictingAlias}'.`); } return false; } this.commandMap.set(command.name, command); (0, logger_1.logInfo)(`Command '${fileName}' (${command.name}) loaded successfully.`); return true; } async setSlashCommands() { try { await this.loadSlashCommandsFromDirectory(); } catch (error) { (0, logger_1.logError)("Failed to set slash commands!"); console.error(error); } } async loadSlashCommandsFromDirectory() { const normalizedDir = this.normalizeDirectoryPath(this.slashCommandsDir); const slashCommandFiles = await this.getValidSlashCommandFiles(normalizedDir); (0, logger_1.logInfo)("Reading slash commands..."); let loadedCount = 0; for (const fileName of slashCommandFiles) { try { const slashCommand = await this.loadSlashCommandFromFile(normalizedDir, fileName); if (slashCommand && this.validateAndAddSlashCommand(slashCommand, fileName)) { loadedCount++; } } catch (error) { (0, logger_1.logError)(`Failed to load slash command '${fileName}'!`); console.error(error); } } (0, logger_1.logInfo)(`${loadedCount.toString()} slash ${loadedCount === 1 ? "command" : "commands"} registered.`); } async getValidSlashCommandFiles(directory) { const dirents = await (0, promises_1.readdir)(directory, { withFileTypes: true }); return dirents .filter(dirent => dirent.isFile()) .map(dirent => dirent.name) .filter(name => this.isValidScriptFile(name, "slash command")); } async loadSlashCommandFromFile(directory, fileName) { const slashCommandPath = `../../../../${directory}/${fileName}`; const slashCommandModule = await import(slashCommandPath); const slashCommandData = slashCommandModule?.default; if (!slashCommandData) { if (!this.suppressWarnings) { (0, logger_1.logWarn)(`'${fileName}' does not have a default export.`); } return null; } if (!(slashCommandData instanceof slashCommandClass_1.SlashCommand)) { if (!this.suppressWarnings) { (0, logger_1.logWarn)(`'${fileName}' does not export a SlashCommand instance as default.`); } return null; } return slashCommandData; } validateAndAddSlashCommand(slashCommand, fileName) { const builderData = slashCommand.convertCommandData(); const commandName = builderData.name; if (typeof commandName !== "string" || !commandName.trim()) { if (!this.suppressWarnings) { (0, logger_1.logWarn)(`'${fileName}' has no valid name set in its builder.`); } return false; } this.slashCommandMap.set(commandName, slashCommand); (0, logger_1.logInfo)(`Slash command '${fileName}' (${commandName}) loaded successfully.`); return true; } normalizeDirectoryPath(directory) { return directory.startsWith("./") ? directory : `./${directory}`; } runDefaultHandler(message, prefixOverride) { const actualPrefix = prefixOverride ?? this.prefix; if (!this.shouldProcessMessage(message, actualPrefix)) { return; } const commandInfo = this.parseCommand(message.content, actualPrefix); if (!commandInfo) { return; } const { commandName, args } = commandInfo; const command = this.getCommandOrAliases(commandName); if (!command) { return; } if (!this.isCommandExecutableInContext(command, message)) { return; } if (!this.handleCooldown(message.author.id, command, message)) { return; } if (!this.handleMaintenance(command, message.author.id, message)) { return; } if (!this.handleDeveloperOnly(command, message.author.id)) { return; } (0, cooldown_1.addCooldown)(message.author.id, command); command.run(message, args); } shouldProcessMessage(message, prefix) { if (message.author.bot) { return false; } if (typeof prefix !== "string") { (0, logger_1.error)("Command handler prefix is not set!"); } return message.content.startsWith(prefix); } parseCommand(content, prefix) { const withoutPrefix = content.slice(prefix.length); const parts = withoutPrefix.split(" "); const commandName = parts[0]; if (!commandName) { return null; } return { commandName, args: parts.slice(1), }; } isCommandExecutableInContext(command, message) { const isInGuild = Boolean(message.guild); if (command.dmOnly && isInGuild) { return false; } if (command.guildOnly && !isInGuild) { return false; } return true; } handleCooldown(userId, command, context) { const [cooldownItem, isValid] = (0, cooldown_1.checkCooldown)(userId, command); if (cooldownItem && !isValid) { if (cooldownItem.messageShown) { return false; } const secondsLeft = Math.ceil((cooldownItem.endsAt - Date.now()) / 1000); const errorMessage = (this.messages.cooldown ?? "Bu komutu kullanmak için **{cooldown}** saniye bekleyiniz.").replace("{cooldown}", secondsLeft.toString()); void context.reply(errorMessage); (0, cooldown_1.editCooldown)(userId, command, { messageShown: true }); return false; } return true; } handleMaintenance(command, userId, context) { if ((this.maintenance || command.maintenance) && !this.developerIds.includes(userId)) { const errorMessage = this.messages.maintenance ?? "Bu komut bakımdadır."; void context.reply(errorMessage); (0, cooldown_1.addCooldown)(userId, command, 5); return false; } return true; } handleDeveloperOnly(command, userId) { return !command.developerOnly || this.developerIds.includes(userId); } runDefaultSlashHandler(interaction) { if (!this.shouldProcessInteraction(interaction)) { return; } const chatInteraction = interaction; const commandName = chatInteraction.commandName; const command = this.getSlashCommand(commandName); if (!command) { return; } if (!this.handleCooldown(chatInteraction.user.id, command, chatInteraction)) { return; } if (!this.handleMaintenance(command, chatInteraction.user.id, chatInteraction)) { return; } if (!this.handleDeveloperOnly(command, chatInteraction.user.id)) { return; } (0, cooldown_1.addCooldown)(chatInteraction.user.id, command); command.run(chatInteraction); } shouldProcessInteraction(interaction) { return !interaction.user.bot && interaction.isChatInputCommand(); } setDefaultHandler(client) { if (!this.suppressWarnings) { (0, logger_1.logWarn)([ "You are using the default command handler included in this package.", "This may cause unexpected errors or restrict you when modifying the command handler.", "For example, you cannot change your bot's prefix per server!", "We recommend using '<CommandHandler>.runDefaultHandler(<Message>, <string?>)' in an event for more flexibility.", "You can ignore this message by setting 'suppressWarnings' to true in the handler constructor.", ].join("\n")); } client.on("messageCreate", this.commandRunner); return this; } removeDefaultHandler(client) { if (!this.suppressWarnings) { (0, logger_1.logWarn)([ "Default command handler removed from the bot.", "Commands will not be detected by the bot.", ].join("\n")); } client.removeListener("messageCreate", this.commandRunner); return this; } setDefaultSlashHandler(client) { if (!this.suppressWarnings) { (0, logger_1.logWarn)([ "You are using the default slash command handler included in this package.", "This may cause unexpected errors or restrict you when modifying the slash command handler.", "We recommend using '<CommandHandler>.runDefaultSlashHandler(<Interaction>)' in an event for more flexibility.", "You can ignore this message by setting 'suppressWarnings' to true in the handler constructor.", ].join("\n")); } client.on("interactionCreate", this.slashCommandRunner); return this; } removeDefaultSlashHandler(client) { if (!this.suppressWarnings) { (0, logger_1.logWarn)([ "Default slash command handler removed from the bot.", "Slash commands will not be detected by the bot.", ].join("\n")); } client.removeListener("interactionCreate", this.slashCommandRunner); return this; } getCommand(commandName) { return this.commandMap.get(commandName); } getCommands() { return Array.from(this.commandMap.values()); } getCommandOrAliases(commandOrAliasName) { return Array.from(this.commandMap.values()).find(command => [command.name, ...command.aliases].includes(commandOrAliasName)); } clearCommands() { this.commandMap.clear(); return this; } removeCommand(commandName) { this.commandMap.delete(commandName); return this; } getSlashCommands() { return Array.from(this.slashCommandMap.values()); } getSlashCommand(slashCommandName) { return this.slashCommandMap.get(slashCommandName); } clearSlashCommands() { this.slashCommandMap.clear(); return this; } async registerSlashCommands(client, guildId) { const commandList = Array.from(this.slashCommandMap.values()) .map(command => command.convertCommandData()); if (typeof guildId === "string") { await client.application?.commands.set(commandList, guildId); } else { await client.application?.commands.set(commandList); } } removeSlashCommand(slashCommandName) { this.slashCommandMap.delete(slashCommandName); return this; } } exports.CommandHandler = CommandHandler;