Compare commits
No commits in common. "db91fcb18b95cb14288f4cc89937a54ffeefffc4" and "e02ffcebed2f5f43e80531e2fedea4fd693a1b2a" have entirely different histories.
db91fcb18b
...
e02ffcebed
122
_opt/botUtils.js
122
_opt/botUtils.js
@ -1,122 +0,0 @@
|
|||||||
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 });
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user