import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js'; /** * botUtils module - provides administrative bot control commands * Currently implements an owner-only exit command for graceful shutdown. */ // Define slash commands export const commands = [ { data: new SlashCommandBuilder() .setName('exit') .setDescription('Gracefully shutdown the bot (owner only)') // Restrict to server administrators by default .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) .setDMPermission(false) .addIntegerOption(option => option .setName('code') .setDescription('Exit code to use (default 0)') .setRequired(false) ), /** * Execute the exit command: only the configured owner can invoke. * @param {import('discord.js').CommandInteraction} interaction * @param {import('discord.js').Client} client */ async execute(interaction, client) { const ownerId = client.config.owner; // Check invoking user is the bot owner if (interaction.user.id !== String(ownerId)) { return interaction.reply({ content: 'Only the bot owner can shutdown the bot.', ephemeral: true }); } // Determine desired exit code (default 0) const exitCode = interaction.options.getInteger('code') ?? 0; // Validate exit code bounds if (exitCode < 0 || exitCode > 254) { return interaction.reply({ content: 'Exit code must be between 0 and 254 inclusive.', ephemeral: true }); } // Acknowledge before shutting down await interaction.reply({ content: `Shutting down with exit code ${exitCode}...`, ephemeral: true }); client.logger.info( `Shutdown initiated by owner ${interaction.user.tag} (${interaction.user.id}), exit code ${exitCode}` ); // Destroy Discord client and exit process try { await client.destroy(); } catch (err) { client.logger.error(`Error during client.destroy(): ${err}`); } process.exit(exitCode); } }, // /status: admin-only, shows current client info { data: new SlashCommandBuilder() .setName('status') .setDescription('Show this bot client status and process info') .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) .setDMPermission(false) .addBooleanOption(option => option .setName('ephemeral') .setDescription('Whether the response should be ephemeral') .setRequired(false) ), async execute(interaction, client) { // Determine if response should be ephemeral (default true) const ephemeral = interaction.options.getBoolean('ephemeral') ?? true; await interaction.deferReply({ ephemeral }); // Process metrics const uptimeSec = process.uptime(); const hours = Math.floor(uptimeSec / 3600); const minutes = Math.floor((uptimeSec % 3600) / 60); const seconds = Math.floor(uptimeSec % 60); const uptime = `${hours}h ${minutes}m ${seconds}s`; const mem = process.memoryUsage(); const toMB = bytes => (bytes / 1024 / 1024).toFixed(2); const memoryInfo = `RSS: ${toMB(mem.rss)} MB, Heap: ${toMB(mem.heapUsed)}/${toMB(mem.heapTotal)} MB`; const cpu = process.cpuUsage(); const cpuInfo = `User: ${(cpu.user / 1000).toFixed(2)} ms, System: ${(cpu.system / 1000).toFixed(2)} ms`; const nodeVersion = process.version; const platform = `${process.platform} ${process.arch}`; // Client-specific stats const guildCount = client.guilds.cache.size; const userCount = client.guilds.cache.reduce((sum, g) => sum + (g.memberCount || 0), 0); const commandCount = client.commands.size; // List of loaded optional modules const loadedModules = client.modules ? Array.from(client.modules.keys()) : []; const lines = [ `Client ID : ${client.config.id}`, `Uptime : ${uptime}`, `Memory : ${memoryInfo}`, `CPU Usage : ${cpuInfo}`, `Node.js : ${nodeVersion}`, `Platform : ${platform}`, `Guilds : ${guildCount}`, `Users : ${userCount}`, `Commands : ${commandCount}`, `Modules : ${loadedModules.length > 0 ? loadedModules.join(', ') : 'None'}` ]; await interaction.editReply({ content: '```\n' + lines.join('\n') + '\n```' }); } } ]; // Remove statusall command: not needed // Enhance status command to list loaded modules // (nothing else follows) // Store all initialized clients for statusall const botUtilsClients = []; /** * Optional init hook; logs when the module is loaded and tracks clients * @param {import('discord.js').Client} client * @param {Object} clientConfig */ export async function init(client, clientConfig) { client.logger.info('botUtils module loaded'); // Track this client instance and its config botUtilsClients.push({ client, clientConfig }); }