diff --git a/discord/classes/Bot.js b/discord/classes/Bot.js index 583f35b..3ef5d61 100644 --- a/discord/classes/Bot.js +++ b/discord/classes/Bot.js @@ -18,36 +18,11 @@ class Bot { partials: ['CHANNEL', 'MESSAGE'] }); - // Add reference to this bot instance on the client for access from commands - this.client.bot = this; - - // THIS IS IMPORTANT: Make sure CommandManager is initialized AFTER the client - // and before using it anywhere else - try { - this.commandManager = new CommandManager(this.client); - console.log("Command Manager initialized successfully"); - } catch (error) { - console.error("Failed to initialize Command Manager:", error); - this.commandManager = null; // Set to null instead of leaving undefined - } + // Initialize command manager + this.commandManager = new CommandManager(this.client); - // Authorized users for commands - Parse comma-separated list from env variable - this.authorizedUserIds = process.env.AUTHORIZED_USER_IDS - ? process.env.AUTHORIZED_USER_IDS.split(',').map(id => id.trim()) - : []; - - // For backward compatibility, add the old env var if it exists - if (process.env.AUTHORIZED_USER_ID && !this.authorizedUserIds.includes(process.env.AUTHORIZED_USER_ID)) { - this.authorizedUserIds.push(process.env.AUTHORIZED_USER_ID); - } - - // Parse notification recipient IDs (separate from command authorization) - this.notificationRecipientIds = process.env.NOTIFICATION_USER_IDS ? - process.env.NOTIFICATION_USER_IDS.split(',').map(id => id.trim()) : - this.authorizedUserIds; // Default to authorized users if not specified - - console.log(`Authorized users configured: ${this.authorizedUserIds.length}`); - console.log(`Notification recipients configured: ${this.notificationRecipientIds.length}`); + // Authorized user ID - CHANGE THIS to your Discord user ID + this.authorizedUserId = process.env.AUTHORIZED_USER_ID; // Setup temp directory this.setupTempDirectory(); @@ -77,17 +52,17 @@ class Bot { this.client.once("ready", async () => { console.log(`Logged in as ${this.client.user.tag}`); - // Add safety check before trying to register commands - if (this.commandManager && typeof this.commandManager.registerGlobalCommands === 'function') { - try { - await this.commandManager.registerGlobalCommands(); - console.log("Global commands registered successfully"); - } catch (error) { - console.error("Error registering global commands:", error); - } - } else { - console.error('Command manager not properly initialized - cannot register commands'); - } + // Only register global commands for direct messages + await this.commandManager.registerGlobalCommands(); + + // Initialize and start the notification service + this.notificationService = new NotificationService(this.client, { + checkInterval: process.env.STATUS_CHECK_INTERVAL ? parseInt(process.env.STATUS_CHECK_INTERVAL) : 5000, + statusEndpoint: process.env.STATUS_ENDPOINT || 'https://blahaj.tr:2589/status' + }); + + await this.notificationService.initialize(); + this.notificationService.start(); // Send startup notification await this.sendStartupNotification(); @@ -96,7 +71,7 @@ class Bot { // Interaction event this.client.on("interactionCreate", async (interaction) => { // Only process commands if the user is authorized - if (!this.authorizedUserIds.includes(interaction.user.id)) { + if (interaction.user.id !== this.authorizedUserId) { console.log(`Unauthorized access attempt by ${interaction.user.tag} (${interaction.user.id})`); await interaction.reply({ content: "You are not authorized to use this bot.", @@ -145,15 +120,14 @@ class Bot { } }; - // Notify all recipients - for (const userId of this.notificationRecipientIds) { - try { - const user = await this.client.users.fetch(userId); - await user.send({ embeds: [startupEmbed] }); - console.log(`Sent startup notification to recipient: ${user.tag}`); - } catch (error) { - console.error(`Failed to send startup notification to user ${userId}:`, error.message); - } + // Only notify the authorized user + try { + const owner = await this.client.users.fetch(this.authorizedUserId); + await owner.send({ embeds: [startupEmbed] }); + console.log(`Sent startup notification to authorized user: ${owner.tag}`); + } catch (error) { + console.error("Failed to send startup notification to authorized user:", error.message); + console.log("This is not critical - the bot will still function normally"); } // Also notify in status channel if configured @@ -170,18 +144,18 @@ class Bot { async sendShutdownNotification(reason = "Manual shutdown", error = null) { // Create shutdown embed const shutdownEmbed = { - title: "Bot Shutdown Notification", + title: "blahaj.tr bot status update", description: `Bot is shutting down at `, color: 0xFF0000, fields: [ { name: "Bot Name", - value: this.client?.user?.tag || "Unknown (Client unavailable)", + value: this.client.user.tag, inline: true }, { name: "Shutdown Reason", - value: reason || "Unknown reason", + value: reason || "Unknown", inline: true }, { @@ -203,30 +177,28 @@ class Bot { }); } - // Only attempt to send notifications if client is ready - if (this.client?.isReady()) { - // Notify all recipients - for (const userId of this.notificationRecipientIds) { - try { - const user = await this.client.users.fetch(userId); - await user.send({ embeds: [shutdownEmbed] }); - console.log(`Sent shutdown notification to recipient: ${user.tag}`); - } catch (error) { - console.error(`Failed to send shutdown notification to user ${userId}:`, error.message); - } - } + // Stop notification service if running + if (this.notificationService?.isRunning) { + this.notificationService.stop(); + } - // Also notify in status channel if available - if (this.notificationService?.statusChannel) { - try { - await this.notificationService.statusChannel.send({ embeds: [shutdownEmbed] }); - console.log(`Sent shutdown notification to status channel: ${this.notificationService.statusChannel.name}`); - } catch (error) { - console.error("Failed to send shutdown notification to status channel:", error.message); - } + // Notify authorized user + try { + const owner = await this.client.users.fetch(this.authorizedUserId); + await owner.send({ embeds: [shutdownEmbed] }); + console.log(`Sent shutdown notification to authorized user: ${owner.tag}`); + } catch (error) { + console.error("Failed to send shutdown notification to authorized user:", error.message); + } + + // Also notify in status channel if available + if (this.notificationService?.statusChannel) { + try { + await this.notificationService.statusChannel.send({ embeds: [shutdownEmbed] }); + console.log(`Sent shutdown notification to status channel: ${this.notificationService.statusChannel.name}`); + } catch (error) { + console.error("Failed to send shutdown notification to status channel:", error.message); } - } else { - console.log("Client not ready, cannot send shutdown notifications"); } } diff --git a/discord/classes/CommandManager.js b/discord/classes/CommandManager.js index 3ef9fbf..8d10b5b 100644 --- a/discord/classes/CommandManager.js +++ b/discord/classes/CommandManager.js @@ -1,4 +1,4 @@ -const { Collection, REST, Routes, SlashCommandBuilder } = require('discord.js'); +const { Collection, REST, Routes } = require('discord.js'); const fs = require('fs'); const path = require('path'); @@ -9,9 +9,6 @@ class CommandManager { this.commandFolders = ['info', 'system']; // Only include info and system commands this.rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN); this.authorizedUserId = process.env.AUTHORIZED_USER_ID; - - // Add this line to load commands when the CommandManager is created - this.loadCommands(); } async loadCommands() { @@ -51,35 +48,26 @@ class CommandManager { async registerGlobalCommands() { try { - console.log("Registering global commands..."); + await this.loadCommands(); - const commandsData = this.commands.map(command => { - const data = { - name: command.name, - description: command.description, - options: command.options || [], - // Add these lines for global availability in all contexts - integration_types: [1], // Add integration type for global availability - contexts: [0, 1, 2], // Available in all contexts (DM, GROUP_DM, GUILD) - }; - - // If the command has an addOptions method, call it - if (typeof command.addOptions === 'function') { - data.options = command.addOptions(new SlashCommandBuilder()).options; - } - - return data; - }); + if (this.commands.size === 0) { + console.log("No commands to register."); + return; + } - const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN); - await rest.put( + const commandsData = this.commands.map(command => command.toJSON()); + + console.log(`Started refreshing ${commandsData.length} application (/) commands.`); + + // Register as global commands for DMs + const data = await this.rest.put( Routes.applicationCommands(this.client.user.id), { body: commandsData }, ); - console.log(`Successfully registered ${commandsData.length} global commands`); + console.log(`Successfully reloaded ${data.length} application (/) commands.`); } catch (error) { - console.error('Error registering global commands:', error); + console.error(error); } } @@ -114,22 +102,6 @@ class CommandManager { } } } - - async handleAutocomplete(interaction) { - const command = this.commands.get(interaction.commandName); - - if (!command || typeof command.handleAutocomplete !== 'function') { - return; - } - - try { - await command.handleAutocomplete(interaction); - } catch (error) { - console.error(`Error handling autocomplete for ${interaction.commandName}:`, error); - // Respond with empty array as fallback - await interaction.respond([]); - } - } } module.exports = CommandManager; diff --git a/discord/classes/NotificationService.js b/discord/classes/NotificationService.js index 5894db8..070a206 100644 --- a/discord/classes/NotificationService.js +++ b/discord/classes/NotificationService.js @@ -3,28 +3,7 @@ const axios = require('axios'); class NotificationService { constructor(client, options = {}) { this.client = client; - - // Parse notification recipient IDs (separate from command authorization) - this.notificationRecipientIds = process.env.NOTIFICATION_USER_IDS ? - process.env.NOTIFICATION_USER_IDS.split(',').map(id => id.trim()) : - []; - - // For backward compatibility - if no notification IDs specified, use authorized IDs - if (this.notificationRecipientIds.length === 0) { - // Use the bot's authorizedUserIds as fallback - this.notificationRecipientIds = client.bot.authorizedUserIds || - (process.env.AUTHORIZED_USER_IDS ? - process.env.AUTHORIZED_USER_IDS.split(',').map(id => id.trim()) : - []); - - // Add legacy single user ID for backward compatibility - if (process.env.AUTHORIZED_USER_ID && !this.notificationRecipientIds.includes(process.env.AUTHORIZED_USER_ID)) { - this.notificationRecipientIds.push(process.env.AUTHORIZED_USER_ID); - } - } - - console.log(`Notification recipients configured: ${this.notificationRecipientIds.length}`); - + this.authorizedUserId = process.env.AUTHORIZED_USER_ID; this.statusChannel = null; this.checkInterval = options.checkInterval || 10000; // Changed to 10 seconds default this.statusEndpoint = options.statusEndpoint || 'https://blahaj.tr:2589/status'; @@ -284,14 +263,14 @@ class NotificationService { } } - // Send to all notification recipients - for (const userId of this.notificationRecipientIds) { + // Send to owner + if (this.authorizedUserId) { try { - const user = await this.client.users.fetch(userId); - await user.send({ embeds: [embed] }); - console.log(`Status change notification sent to recipient: ${user.tag}`); + const owner = await this.client.users.fetch(this.authorizedUserId); + await owner.send({ embeds: [embed] }); + console.log('Status change notification sent to owner'); } catch (error) { - console.error(`Failed to send status notification to user ${userId}: ${error.message}`); + console.error(`Failed to send status notification to owner: ${error.message}`); } } } diff --git a/discord/commands/system/pm2.js b/discord/commands/system/pm2.js deleted file mode 100644 index 99d32b5..0000000 --- a/discord/commands/system/pm2.js +++ /dev/null @@ -1,539 +0,0 @@ -const SystemCommandBase = require('../../classes/SystemCommandBase'); -const { SlashCommandBuilder } = require('discord.js'); - -class PM2Control extends SystemCommandBase { - constructor(client) { - super(client); - this.name = 'pm2'; - this.description = 'Control PM2 processes'; - } - - addOptions(builder) { - return builder - .addSubcommand(subcommand => - subcommand - .setName('list') - .setDescription('List all PM2 processes') - ) - .addSubcommand(subcommand => - subcommand - .setName('info') - .setDescription('Get detailed information about a PM2 process') - .addStringOption(option => - option - .setName('process') - .setDescription('Process name or ID') - .setRequired(true) - .setAutocomplete(true) - ) - ) - .addSubcommand(subcommand => - subcommand - .setName('start') - .setDescription('Start a PM2 process') - .addStringOption(option => - option - .setName('process') - .setDescription('Process name or ID') - .setRequired(true) - .setAutocomplete(true) - ) - ) - .addSubcommand(subcommand => - subcommand - .setName('stop') - .setDescription('Stop a PM2 process') - .addStringOption(option => - option - .setName('process') - .setDescription('Process name or ID') - .setRequired(true) - .setAutocomplete(true) - ) - ) - .addSubcommand(subcommand => - subcommand - .setName('restart') - .setDescription('Restart a PM2 process') - .addStringOption(option => - option - .setName('process') - .setDescription('Process name or ID') - .setRequired(true) - .setAutocomplete(true) - ) - ) - .addSubcommand(subcommand => - subcommand - .setName('logs') - .setDescription('Show recent logs for a PM2 process') - .addStringOption(option => - option - .setName('process') - .setDescription('Process name or ID') - .setRequired(true) - .setAutocomplete(true) - ) - .addIntegerOption(option => - option - .setName('lines') - .setDescription('Number of log lines to show') - .setRequired(false) - ) - ); - } - - async execute(interaction) { - await interaction.deferReply(); - - const subcommand = interaction.options.getSubcommand(); - - try { - switch (subcommand) { - case 'list': - await this.handleListCommand(interaction); - break; - - case 'info': - await this.handleInfoCommand(interaction); - break; - - case 'start': - await this.handleStartCommand(interaction); - break; - - case 'stop': - await this.handleStopCommand(interaction); - break; - - case 'restart': - await this.handleRestartCommand(interaction); - break; - - case 'logs': - await this.handleLogsCommand(interaction); - break; - - default: - await interaction.editReply(`Unknown subcommand: ${subcommand}`); - } - } catch (error) { - console.error(`Error executing PM2 command:`, error); - await interaction.editReply({ - content: `Error executing command: ${error.message}` - }); - } - } - - async handleListCommand(interaction) { - const { stdout } = await this.execCommand('pm2 jlist'); - - try { - const processes = JSON.parse(stdout); - - if (processes.length === 0) { - await interaction.editReply('No PM2 processes found.'); - return; - } - - const embed = { - title: 'PM2 Process List', - color: 0x3498db, - fields: [], - timestamp: new Date(), - footer: { text: 'PM2 Process Manager' } - }; - - processes.forEach(proc => { - // Format memory to MB - const memory = Math.round(proc.monit.memory / (1024 * 1024) * 10) / 10; - - // Get appropriate status emoji - let statusEmoji = '⚪'; - switch (proc.pm2_env.status) { - case 'online': statusEmoji = '🟢'; break; - case 'stopping': statusEmoji = '🟠'; break; - case 'stopped': statusEmoji = '🔴'; break; - case 'errored': statusEmoji = '❌'; break; - case 'launching': statusEmoji = '🟡'; break; - } - - // Calculate uptime - const uptime = proc.pm2_env.status === 'online' ? - this.formatUptime(Date.now() - proc.pm2_env.pm_uptime) : - 'Not running'; - - embed.fields.push({ - name: `${statusEmoji} ${proc.name} (ID: ${proc.pm_id})`, - value: [ - `**Status:** ${proc.pm2_env.status}`, - `**CPU:** ${proc.monit.cpu}%`, - `**Memory:** ${memory} MB`, - `**Uptime:** ${uptime}`, - `**Restarts:** ${proc.pm2_env.restart_time}` - ].join('\n'), - inline: true - }); - }); - - await interaction.editReply({ embeds: [embed] }); - - } catch (error) { - console.error('Error parsing PM2 process list:', error); - await interaction.editReply({ - content: `Failed to parse PM2 process list: ${error.message}`, - files: stdout.length > 0 ? [{ - attachment: Buffer.from(stdout), - name: 'pm2-list.json' - }] : [] - }); - } - } - - async handleInfoCommand(interaction) { - const processName = interaction.options.getString('process'); - - // Get detailed info about the process - const { success, stdout, stderr } = await this.execCommand(`pm2 show ${processName} --format json`); - - if (!success) { - await interaction.editReply(`Failed to get info for PM2 process "${processName}":\n\`\`\`${stderr}\`\`\``); - return; - } - - try { - // Parse the JSON output - const procInfo = JSON.parse(stdout); - - // Get status emoji - let statusEmoji = '⚪'; - switch (procInfo.status) { - case 'online': statusEmoji = '🟢'; break; - case 'stopping': statusEmoji = '🟠'; break; - case 'stopped': statusEmoji = '🔴'; break; - case 'errored': statusEmoji = '❌'; break; - case 'launching': statusEmoji = '🟡'; break; - } - - // Format memory - const memory = procInfo.memory ? - Math.round(procInfo.memory / (1024 * 1024) * 10) / 10 : - 0; - - // Create embed - const embed = { - title: `${statusEmoji} PM2 Process: ${procInfo.name}`, - color: procInfo.status === 'online' ? 0x00FF00 : 0xFF0000, - fields: [ - { - name: 'General', - value: [ - `**ID:** ${procInfo.pm_id}`, - `**Status:** ${procInfo.status}`, - `**Version:** ${procInfo.version || 'N/A'}`, - `**Instances:** ${procInfo.exec_instances || 1}`, - `**Exec Mode:** ${procInfo.exec_mode || 'N/A'}` - ].join('\n'), - inline: true - }, - { - name: 'Resources', - value: [ - `**CPU:** ${procInfo.cpu || 0}%`, - `**Memory:** ${memory} MB`, - `**Uptime:** ${this.formatUptime(procInfo.pm_uptime) || 'Not running'}`, - `**Restarts:** ${procInfo.restart_time || 0}`, - `**Unstable Restarts:** ${procInfo.unstable_restarts || 0}` - ].join('\n'), - inline: true - }, - { - name: 'Paths', - value: [ - `**Path:** ${procInfo.path || 'N/A'}`, - `**Current Path:** ${procInfo.cwd || 'N/A'}`, - `**Script:** ${procInfo.script || 'N/A'}` - ].join('\n'), - inline: false - } - ], - timestamp: new Date(), - footer: { text: 'PM2 Process Manager' } - }; - - // Add logs section if available - if (procInfo.out_log_path || procInfo.error_log_path) { - embed.fields.push({ - name: 'Logs', - value: [ - `**Output:** ${procInfo.out_log_path || 'N/A'}`, - `**Error:** ${procInfo.error_log_path || 'N/A'}` - ].join('\n'), - inline: false - }); - } - - await interaction.editReply({ embeds: [embed] }); - - } catch (error) { - console.error('Error parsing PM2 process info:', error); - await interaction.editReply(`Failed to parse info for PM2 process "${processName}":\n\`\`\`${error.message}\`\`\``); - } - } - - async handleStartCommand(interaction) { - const processName = interaction.options.getString('process'); - - // First get current status - const { success: infoSuccess, stdout: infoStdout } = await this.execCommand(`pm2 jlist`); - let beforeStatus = 'unknown'; - - if (infoSuccess) { - try { - const processes = JSON.parse(infoStdout); - const proc = processes.find(p => p.name === processName || p.pm_id.toString() === processName); - if (proc) { - beforeStatus = proc.pm2_env.status; - } - } catch (error) { - console.error('Error parsing PM2 process list before start:', error); - } - } - - // Start the process - await interaction.editReply(`Starting PM2 process \`${processName}\`...`); - const { success, stdout, stderr } = await this.execCommand(`pm2 start ${processName}`); - - if (!success) { - await interaction.editReply(`Failed to start PM2 process "${processName}":\n\`\`\`${stderr}\`\`\``); - return; - } - - // Get new status - const { success: newInfoSuccess, stdout: newInfoStdout } = await this.execCommand(`pm2 jlist`); - let afterStatus = 'unknown'; - - if (newInfoSuccess) { - try { - const processes = JSON.parse(newInfoStdout); - const proc = processes.find(p => p.name === processName || p.pm_id.toString() === processName); - if (proc) { - afterStatus = proc.pm2_env.status; - } - } catch (error) { - console.error('Error parsing PM2 process list after start:', error); - } - } - - // Create status emoji - let statusEmoji = '⚪'; - switch (afterStatus) { - case 'online': statusEmoji = '🟢'; break; - case 'stopping': statusEmoji = '🟠'; break; - case 'stopped': statusEmoji = '🔴'; break; - case 'errored': statusEmoji = '❌'; break; - case 'launching': statusEmoji = '🟡'; break; - } - - await interaction.editReply(`PM2 process \`${processName}\` started.\n\nStatus: ${statusEmoji} ${afterStatus}\nPrevious status: ${beforeStatus}`); - } - - async handleStopCommand(interaction) { - const processName = interaction.options.getString('process'); - - // First get current status - const { success: infoSuccess, stdout: infoStdout } = await this.execCommand(`pm2 jlist`); - let beforeStatus = 'unknown'; - - if (infoSuccess) { - try { - const processes = JSON.parse(infoStdout); - const proc = processes.find(p => p.name === processName || p.pm_id.toString() === processName); - if (proc) { - beforeStatus = proc.pm2_env.status; - } - } catch (error) { - console.error('Error parsing PM2 process list before stop:', error); - } - } - - // Stop the process - await interaction.editReply(`Stopping PM2 process \`${processName}\`...`); - const { success, stdout, stderr } = await this.execCommand(`pm2 stop ${processName}`); - - if (!success) { - await interaction.editReply(`Failed to stop PM2 process "${processName}":\n\`\`\`${stderr}\`\`\``); - return; - } - - // Get new status - const { success: newInfoSuccess, stdout: newInfoStdout } = await this.execCommand(`pm2 jlist`); - let afterStatus = 'unknown'; - - if (newInfoSuccess) { - try { - const processes = JSON.parse(newInfoStdout); - const proc = processes.find(p => p.name === processName || p.pm_id.toString() === processName); - if (proc) { - afterStatus = proc.pm2_env.status; - } - } catch (error) { - console.error('Error parsing PM2 process list after stop:', error); - } - } - - // Create status emoji - let statusEmoji = '⚪'; - switch (afterStatus) { - case 'online': statusEmoji = '🟢'; break; - case 'stopping': statusEmoji = '🟠'; break; - case 'stopped': statusEmoji = '🔴'; break; - case 'errored': statusEmoji = '❌'; break; - case 'launching': statusEmoji = '🟡'; break; - } - - await interaction.editReply(`PM2 process \`${processName}\` stopped.\n\nStatus: ${statusEmoji} ${afterStatus}\nPrevious status: ${beforeStatus}`); - } - - async handleRestartCommand(interaction) { - const processName = interaction.options.getString('process'); - - // First get current status - const { success: infoSuccess, stdout: infoStdout } = await this.execCommand(`pm2 jlist`); - let beforeStatus = 'unknown'; - - if (infoSuccess) { - try { - const processes = JSON.parse(infoStdout); - const proc = processes.find(p => p.name === processName || p.pm_id.toString() === processName); - if (proc) { - beforeStatus = proc.pm2_env.status; - } - } catch (error) { - console.error('Error parsing PM2 process list before restart:', error); - } - } - - // Restart the process - await interaction.editReply(`Restarting PM2 process \`${processName}\`...`); - const { success, stdout, stderr } = await this.execCommand(`pm2 restart ${processName}`); - - if (!success) { - await interaction.editReply(`Failed to restart PM2 process "${processName}":\n\`\`\`${stderr}\`\`\``); - return; - } - - // Get new status - const { success: newInfoSuccess, stdout: newInfoStdout } = await this.execCommand(`pm2 jlist`); - let afterStatus = 'unknown'; - - if (newInfoSuccess) { - try { - const processes = JSON.parse(newInfoStdout); - const proc = processes.find(p => p.name === processName || p.pm_id.toString() === processName); - if (proc) { - afterStatus = proc.pm2_env.status; - } - } catch (error) { - console.error('Error parsing PM2 process list after restart:', error); - } - } - - // Create status emoji - let statusEmoji = '⚪'; - switch (afterStatus) { - case 'online': statusEmoji = '🟢'; break; - case 'stopping': statusEmoji = '🟠'; break; - case 'stopped': statusEmoji = '🔴'; break; - case 'errored': statusEmoji = '❌'; break; - case 'launching': statusEmoji = '🟡'; break; - } - - await interaction.editReply(`PM2 process \`${processName}\` restarted.\n\nStatus: ${statusEmoji} ${afterStatus}\nPrevious status: ${beforeStatus}`); - } - - async handleLogsCommand(interaction) { - const processName = interaction.options.getString('process'); - const lines = interaction.options.getInteger('lines') || 20; - - // Get logs for the process - await interaction.editReply(`Fetching logs for PM2 process \`${processName}\`...`); - const { success, stdout, stderr } = await this.execCommand(`pm2 logs ${processName} --lines ${lines} --nostream --raw`); - - if (!success) { - await interaction.editReply(`Failed to get logs for PM2 process "${processName}":\n\`\`\`${stderr}\`\`\``); - return; - } - - // Format the logs - const logs = stdout.trim(); - - if (!logs) { - await interaction.editReply(`No logs found for PM2 process \`${processName}\`.`); - return; - } - - // If logs are too long, split into files - if (logs.length > 1950) { - await interaction.editReply({ - content: `Logs for PM2 process \`${processName}\` (last ${lines} lines):`, - files: [{ - attachment: Buffer.from(logs), - name: `${processName}-logs.txt` - }] - }); - } else { - await interaction.editReply(`Logs for PM2 process \`${processName}\` (last ${lines} lines):\n\`\`\`\n${logs}\n\`\`\``); - } - } - - // Helper method to autocomplete process names - async handleAutocomplete(interaction) { - try { - const focusedValue = interaction.options.getFocused(); - const { success, stdout } = await this.execCommand('pm2 jlist'); - - if (!success) { - return interaction.respond([]); - } - - const processes = JSON.parse(stdout); - const choices = processes.map(proc => ({ - name: `${proc.name} (${proc.pm2_env.status})`, - value: proc.name - })); - - // Filter choices based on user input - const filtered = choices.filter(choice => - choice.name.toLowerCase().includes(focusedValue.toLowerCase()) - ); - - await interaction.respond(filtered.slice(0, 25)); - } catch (error) { - console.error('Error in PM2 autocomplete:', error); - await interaction.respond([]); - } - } - - // Helper to format uptime - formatUptime(ms) { - if (!ms || ms <= 0) return 'Not running'; - - const seconds = Math.floor(ms / 1000); - const minutes = Math.floor(seconds / 60); - const hours = Math.floor(minutes / 60); - const days = Math.floor(hours / 24); - - if (days > 0) { - return `${days}d ${hours % 24}h ${minutes % 60}m`; - } else if (hours > 0) { - return `${hours}h ${minutes % 60}m ${seconds % 60}s`; - } else if (minutes > 0) { - return `${minutes}m ${seconds % 60}s`; - } else { - return `${seconds}s`; - } - } -} - -module.exports = PM2Control; diff --git a/index.js b/index.js index bf703fb..510db2c 100644 --- a/index.js +++ b/index.js @@ -51,31 +51,8 @@ async function shutdown(signal) { } // Register shutdown handlers -process.on('SIGINT', async () => { - console.log('Received SIGINT. Shutting down gracefully...'); - try { - // If you have the bot instance available, call shutdown method - if (global.discordBot) { - await global.discordBot.sendShutdownNotification("SIGINT received"); - } - } catch (error) { - console.error("Error shutting down Discord bot:", error); - } - process.exit(0); -}); - -process.on('SIGTERM', async () => { - console.log('Received SIGTERM. Shutting down gracefully...'); - try { - // If you have the bot instance available, call shutdown method - if (global.discordBot) { - await global.discordBot.sendShutdownNotification("SIGTERM received"); - } - } catch (error) { - console.error("Error shutting down Discord bot:", error); - } - process.exit(0); -}); +process.on('SIGINT', () => shutdown('SIGINT')); +process.on('SIGTERM', () => shutdown('SIGTERM')); // Catch uncaught exceptions process.on('uncaughtException', (error) => {