diff --git a/_opt/botUtils.js b/_opt/botUtils.js index 3a6f3ec..b75e83c 100644 --- a/_opt/botUtils.js +++ b/_opt/botUtils.js @@ -1,4 +1,4 @@ -import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js'; +import { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder } from 'discord.js'; /** * botUtils module - provides administrative bot control commands @@ -86,102 +86,69 @@ export const commands = [ const commandCount = client.commands.size; // List of loaded optional modules const loadedModules = client.modules ? Array.from(client.modules.keys()) : []; - // Format output as Markdown lists - const sections = []; - sections.push('**Process Metrics**'); - sections.push(`- Uptime: ${uptime}`); - sections.push(`- Memory: ${memoryInfo}`); - sections.push(`- CPU Usage: ${cpuInfo}`); - sections.push(`- Node.js: ${nodeVersion}`); - sections.push(`- Platform: ${platform}`); - sections.push(''); - sections.push(`- ${client.config.id}`); - sections.push(` - Guilds: ${guildCount}`); - sections.push(` - Users: ${userCount}`); - sections.push(` - Commands: ${commandCount}`); - sections.push(` - Modules: ${loadedModules.length > 0 ? loadedModules.join(', ') : 'None'}`); - await interaction.editReply({ content: sections.join('\n') }); - client.logger.info(`[cmd:status] Returned status for client ${client.config.id}`); - } - }, - // /statusall: show status for all initialized bot clients - { - data: new SlashCommandBuilder() - .setName('statusall') - .setDescription('Show status for all bot clients') - .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) - .setDMPermission(false) - .addBooleanOption(option => - option - .setName('ephemeral') - .setDescription('Whether the response should be ephemeral') - .setRequired(false) - ), - async execute(interaction, client) { - // Only the bot owner may run this command - const ownerId = client.config.owner; - if (interaction.user.id !== String(ownerId)) { - return interaction.reply({ content: 'Only the bot owner can use this command.', ephemeral: true }); + // Build embed for status + // Determine if gitUtils module is loaded + const gitLoaded = client.modules?.has('gitUtils'); + let branch, build, statusBlock; + if (gitLoaded) { + const git = client.modules.get('gitUtils'); + try { + branch = await git.getBranch(); + build = await git.getShortHash(); + const statusRaw = await git.getStatusShort(); + // Format status as fenced code block using template literals + // Normalize each line with a leading space in a code fence + // Prefix raw status output with a single space + statusBlock = statusRaw + ? '```\n ' + statusRaw + '\n```' + : '```\n (clean)\n```'; + } catch { + branch = 'error'; + build = 'error'; + // Represent error status in code fence + statusBlock = '```\n (error)\n```'; + } } - 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}`; - // Format output as Markdown lists - const sections = []; - sections.push('**Process Metrics**'); - sections.push(`- Uptime: ${uptime}`); - sections.push(`- Memory: ${memoryInfo}`); - sections.push(`- CPU Usage: ${cpuInfo}`); - sections.push(`- Node.js: ${nodeVersion}`); - sections.push(`- Platform: ${platform}`); - sections.push(''); - sections.push('**Clients**'); - for (const entry of botUtilsClients) { - const c = entry.client; - const cfg = entry.clientConfig; - const guildCount = c.guilds.cache.size; - const userCount = c.guilds.cache.reduce((sum, g) => sum + (g.memberCount || 0), 0); - const commandCount = c.commands.size; - const modules = c.modules ? Array.from(c.modules.keys()) : []; - sections.push(`- ${cfg.id}`); - sections.push(` - Guilds: ${guildCount}`); - sections.push(` - Users: ${userCount}`); - sections.push(` - Commands: ${commandCount}`); - sections.push(` - Modules: ${modules.length > 0 ? modules.join(', ') : 'None'}`); - sections.push(''); + // Prepare module list as bullet points + const moduleList = loadedModules.length > 0 + ? loadedModules.map(m => `• ${m}`).join('\n') + : 'None'; + // Assemble fields + const fields = []; + // Client identification + fields.push({ name: 'Client', value: client.config.id, inline: false }); + // Performance metrics + fields.push({ name: 'CPU Usage', value: cpuInfo, inline: false }); + fields.push({ name: 'Memory', value: memoryInfo, inline: false }); + // Environment + fields.push({ name: 'Node.js', value: nodeVersion, inline: true }); + fields.push({ name: 'Platform', value: platform, inline: true }); + // Uptime + fields.push({ name: 'Uptime', value: uptime, inline: true }); + // Loaded modules + fields.push({ name: 'Modules', value: moduleList, inline: false }); + // Entity counts + fields.push({ name: 'Commands', value: commandCount.toString(), inline: true }); + fields.push({ name: 'Guilds', value: guildCount.toString(), inline: true }); + fields.push({ name: 'Users', value: userCount.toString(), inline: true }); + // Git reference and status if available + if (gitLoaded) { + fields.push({ name: 'Git Reference', value: `${branch}/${build}`, inline: false }); + fields.push({ name: 'Git Status', value: statusBlock, inline: false }); } - await interaction.editReply({ content: sections.join('\n') }); - client.logger.info(`[cmd:statusall] Returned status for all ${botUtilsClients.length} clients`); + // Create embed + const embed = new EmbedBuilder() + .setAuthor({ name: 'ClientX', iconURL: client.user.displayAvatarURL() }) + .setThumbnail(client.user.displayAvatarURL()) + .addFields(fields) + .setTimestamp(); + await interaction.editReply({ embeds: [embed] }); + client.logger.info(`[cmd:status] Returned status embed for client ${client.config.id}`); } } ]; -// Note: statusall command implemented above - -// 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 - */ +// Module loaded logging export async function init(client, clientConfig) { client.logger.info('[module:botUtils] Module loaded'); - // Track this client instance and its config - botUtilsClients.push({ client, clientConfig }); } \ No newline at end of file diff --git a/_opt/gitUtils.js b/_opt/gitUtils.js index 3fd950a..2962ee6 100644 --- a/_opt/gitUtils.js +++ b/_opt/gitUtils.js @@ -99,4 +99,48 @@ export const commands = [ // No special init logic export async function init(client) { client.logger.warn('[module:gitUtils] Git utilities module loaded - dangerous module, use with caution'); +} +// Helper functions for external use +/** + * Get current Git branch name + * @returns {Promise} + */ +export async function getBranch() { + return runGit(['rev-parse', '--abbrev-ref', 'HEAD']); +} +/** + * Get short commit hash of HEAD + * @returns {Promise} + */ +export async function getShortHash() { + return runGit(['rev-parse', '--short', 'HEAD']); +} +/** + * Get concise working tree status (git status --porcelain) + * @returns {Promise} + */ +export async function getStatusShort() { + return runGit(['status', '--porcelain']); +} +/** + * Get Git remote origin URL + * @returns {Promise} + */ +export async function getRemoteUrl() { + return runGit(['config', '--get', 'remote.origin.url']); +} +/** + * Get recent commit log (n lines, one-line format) + * @param {number} [n=5] + * @returns {Promise} + */ +export async function getLog(n = 5) { + return runGit(['log', `-n${n}`, '--oneline']); +} +/** + * Get diff summary (git diff --stat) + * @returns {Promise} + */ +export async function getDiffStat() { + return runGit(['diff', '--stat']); } \ No newline at end of file