rashi-discord-bot-lib
Version:
🚀 Powerful Discord bot framework with built-in database, event handling, and utilities
222 lines • 9.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommandLoader = void 0;
// src/structures/CommandLoader.ts
const discord_js_1 = require("discord.js");
const FileLoader_1 = require("../utils/FileLoader");
const Logger_1 = require("../utils/Logger");
// Guards
const commandGuards_1 = require("../utils/commandGuards");
const mongo_1 = require("../utils/mongo");
class CommandLoader {
logger;
fileLoader;
slashCommands;
prefixCommands;
statistics;
cooldowns;
constructor(logger) {
this.logger = logger || new Logger_1.Logger();
this.fileLoader = new FileLoader_1.FileLoader();
this.slashCommands = new discord_js_1.Collection();
this.prefixCommands = new discord_js_1.Collection();
}
setCooldowns(cooldowns) {
this.cooldowns = cooldowns;
}
setStatistics(statistics) {
this.statistics = statistics;
}
async loadCommands(client, config) {
let slashSize = 0;
let prefixSize = 0;
try {
if (config?.slashPath) {
slashSize = await this.loadSlashCommands(client, config.slashPath);
}
if (config?.prefixPath) {
prefixSize = await this.loadPrefixCommands(client, config.prefixPath, config.prefix || '!');
}
return { slashSize, prefixSize };
}
catch (error) {
this.logger.error('Failed to load commands:', error);
return { slashSize: 0, prefixSize: 0 };
}
}
async loadSlashCommands(client, commandsPath) {
const result = await this.fileLoader.loadFiles(commandsPath, {
extensions: ['.js', '.ts'],
recursive: true,
requireDefault: true,
});
const commandsToRegister = [];
let registeredCount = 0;
for (const command of result.files) {
if (this.validateSlashCommand(command)) {
this.slashCommands.set(command.data.name, command);
commandsToRegister.push(command.data.toJSON());
registeredCount++;
}
}
if (commandsToRegister.length > 0) {
await this.registerSlashCommands(client, commandsToRegister);
}
this.setupSlashCommandHandler(client);
if (result.errors.length > 0) {
this.logger.warn(`Failed to load ${result.errors.length} slash command files`);
}
return registeredCount;
}
async loadPrefixCommands(client, commandsPath, prefix) {
const result = await this.fileLoader.loadFiles(commandsPath, {
extensions: ['.js', '.ts'],
recursive: true,
requireDefault: true,
});
let registeredCount = 0;
for (const command of result.files) {
if (this.validatePrefixCommand(command)) {
this.prefixCommands.set(command.name.toLowerCase(), command);
registeredCount++;
}
}
this.setupPrefixCommandHandler(client, prefix);
if (result.errors.length > 0) {
this.logger.warn(`Failed to load ${result.errors.length} prefix command files`);
}
return registeredCount;
}
validateSlashCommand(command) {
return command && typeof command === 'object' && command.data?.name && typeof command.execute === 'function';
}
validatePrefixCommand(command) {
return command && typeof command === 'object' && typeof command.name === 'string' && typeof command.execute === 'function';
}
async registerSlashCommands(client, commands) {
const appId = client.application?.id || client.user?.id || process.env.CLIENT_ID || '';
if (!appId)
return;
const token = client.token || process.env.BOT_TOKEN || process.env.DISCORD_TOKEN || '';
const rest = new discord_js_1.REST({ version: '10' }).setToken(token);
try {
await rest.put(discord_js_1.Routes.applicationCommands(appId), { body: commands });
}
catch (e) {
this.logger.warn('Slash registration failed, retrying on ready');
client.once('ready', async () => {
try {
await rest.put(discord_js_1.Routes.applicationCommands(appId), { body: commands });
}
catch (err) {
this.logger.error('Failed again on ready:', err);
}
});
}
}
setupSlashCommandHandler(client) {
client.on('interactionCreate', async (interaction) => {
if (interaction.isAutocomplete()) {
const cmd = this.slashCommands.get(interaction.commandName);
if (cmd?.autocomplete)
await cmd.autocomplete(interaction);
return;
}
if (!interaction.isChatInputCommand())
return;
const command = this.slashCommands.get(interaction.commandName);
if (!command)
return;
const startTime = Date.now();
try {
const ok = await (0, commandGuards_1.enforceSlashGuards)(interaction, command.data.name, command, this.cooldowns);
if (!ok)
return;
const language = interaction.locale ?? 'en';
await command.execute(interaction, client, language);
const responseTime = Date.now() - startTime;
this.statistics?.trackCommand(command.data.name, 'slash', responseTime);
await this.bumpCommandCounters('slash', command.data.name, interaction.guildId ?? null);
}
catch (error) {
this.statistics?.trackError();
this.logger.error(`Error executing slash command ${interaction.commandName}:`, error);
const msg = 'There was an error while executing this command!';
if (interaction.replied || interaction.deferred) {
await interaction.followUp({ content: msg, ephemeral: true });
}
else {
await interaction.reply({ content: msg, ephemeral: true });
}
}
});
}
setupPrefixCommandHandler(client, prefix) {
client.on('messageCreate', async (message) => {
if (message.author.bot)
return;
const content = message.content.trim();
const language = message.guild?.preferredLocale || 'en';
if (!content.startsWith(prefix)) {
const fast = [...this.prefixCommands.values()].find((cmd) => cmd.fastUse && [cmd.name, ...(cmd.aliases ?? [])].map(n => n.toLowerCase()).includes(content.toLowerCase()));
if (fast) {
try {
const ok = await (0, commandGuards_1.enforcePrefixGuards)(message, fast.name, fast, this.cooldowns);
if (!ok)
return;
await fast.execute(message, [], client, language);
this.statistics?.trackCommand(fast.name, 'prefix', 0);
await this.bumpCommandCounters('prefix', fast.name, message.guildId ?? null);
}
catch (e) {
this.statistics?.trackError();
this.logger.error(`Error executing fastUse command ${fast.name}:`, e);
await message.reply('There was an error while executing this command!');
}
}
return;
}
const args = content.slice(prefix.length).trim().split(/ +/);
const name = args.shift()?.toLowerCase();
if (!name)
return;
const command = this.prefixCommands.get(name) ||
[...this.prefixCommands.values()].find((cmd) => cmd.aliases?.map((a) => a.toLowerCase()).includes(name));
if (!command)
return;
const startTime = Date.now();
try {
const ok = await (0, commandGuards_1.enforcePrefixGuards)(message, command.name, command, this.cooldowns);
if (!ok)
return;
await command.execute(message, args, client, language);
const responseTime = Date.now() - startTime;
this.statistics?.trackCommand(command.name, 'prefix', responseTime);
await this.bumpCommandCounters('prefix', command.name, message.guildId ?? null);
}
catch (error) {
this.statistics?.trackError();
this.logger.error(`Error executing prefix command ${name}:`, error);
await message.reply('There was an error while executing this command!');
}
});
}
async bumpCommandCounters(kind, name, guildId) {
try {
const db = (0, mongo_1.getDb)();
const col = db.collection('commandMetrics');
const now = new Date();
await col.updateOne({ scope: 'total' }, { $inc: { executed: 1 }, $set: { updatedAt: now }, $setOnInsert: { createdAt: now, executed: 0 } }, { upsert: true });
await col.updateOne({ scope: 'byCommand', kind, name, guildId: guildId ?? null }, { $inc: { executed: 1 }, $set: { updatedAt: now }, $setOnInsert: { createdAt: now, executed: 0 } }, { upsert: true });
}
catch { }
}
getSlashCommands() {
return this.slashCommands;
}
getPrefixCommands() {
return this.prefixCommands;
}
}
exports.CommandLoader = CommandLoader;
//# sourceMappingURL=CommandLoader.js.map