All files / src/handlers BotControlHandler.js

100% Statements 62/62
100% Branches 34/34
100% Functions 5/5
100% Lines 62/62

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142                24x       3x 1x 2x 1x             3x 1x 2x 1x         9x 9x 9x   9x 1x 1x         8x 1x 1x 1x       6x 6x 1x 1x       5x 2x 2x       3x 3x 1x 1x       2x   2x             2x     1x 1x         8x 8x 8x   8x 1x 1x       7x 1x 1x 1x       5x 5x 1x 1x       4x 2x 2x       2x 2x 1x 1x       1x   1x             1x     1x 1x         8x
/**
 * BotControlHandler - Handles bot control commands (enable/disable)
 * Allows administrators to control bot state using run IDs for multiple deployment management
 * This enables blue-green deployments where multiple bot instances can run simultaneously
 * but only one needs to be active at a time
 */
class BotControlHandler {
    constructor(bot) {
        this.bot = bot;
    }
 
    async handleModeratorCommand(message, member, content) {
        if (content.startsWith('!boton ')) {
            await this.handleBotOn(message, content.substring(7));
        } else if (content.startsWith('!botoff ')) {
            await this.handleBotOff(message, content.substring(8));
        }
    }
 
    async handleMemberCommand(message, member, content) {
        // Bot control commands are duplicated in member channels to provide flexibility
        // This allows administrators to control bots from either channel context
        if (content.startsWith('!boton ')) {
            await this.handleBotOn(message, content.substring(7));
        } else if (content.startsWith('!botoff ')) {
            await this.handleBotOff(message, content.substring(8));
        }
    }
 
    async handleBotOn(message, args) {
        try {
            const guild = message.guild;
            const member = guild.members.cache.get(message.author.id);
            
            if (!member) {
                await message.reply('❌ Could not find your membership in this server.');
                return;
            }
 
            // Restrict to administrators only for security reasons
            // Bot control affects the entire server's functionality and could disrupt operations
            if (!member.permissions.has('Administrator')) {
                await message.reply('❌ This command is restricted to administrators only.');
                console.log(`🚨 Non-admin ${message.author.tag} attempted to use !boton command`);
                return;
            }
 
            // Validate run ID parameter
            const runId = args.trim();
            if (!runId) {
                await message.reply('❌ Please provide a run ID. Usage: `!boton <run_id>`');
                return;
            }
 
            // Validate run ID format (alphanumeric string)
            if (!/^[a-zA-Z0-9\-_]+$/.test(runId) || runId.length < 3 || runId.length > 50) {
                await message.reply('❌ Invalid run ID format. Please provide a valid run ID from Terraform.');
                return;
            }
 
            // Check if this command is for this bot instance
            const currentRunId = this.bot.getRunId();
            if (runId !== currentRunId) {
                console.log(`Ignoring !boton command for different bot instance. Current: ${currentRunId}, Requested: ${runId}`);
                return; // Silently ignore commands for other bot instances
            }
 
            // Enable the bot
            this.bot.enableBot(this.bot.getBotId());
 
            await message.reply(`✅ **Bot Control Update**
🤖 **Run ID:** \`${runId}\`
🟢 **Status:** Enabled
👤 **Administrator:** ${message.author.tag}
 
The bot will now respond to all commands normally.`);
 
            console.log(`✅ Administrator ${message.author.tag} enabled bot ${runId}`);
 
        } catch (error) {
            console.error('Error handling boton command:', error);
            await message.reply('❌ An error occurred while enabling the bot.');
        }
    }
 
    async handleBotOff(message, args) {
        try {
            const guild = message.guild;
            const member = guild.members.cache.get(message.author.id);
            
            if (!member) {
                await message.reply('❌ Could not find your membership in this server.');
                return;
            }
 
            // Check if user is administrator (highest permission level)
            if (!member.permissions.has('Administrator')) {
                await message.reply('❌ This command is restricted to administrators only.');
                console.log(`🚨 Non-admin ${message.author.tag} attempted to use !botoff command`);
                return;
            }
 
            // Validate run ID parameter
            const runId = args.trim();
            if (!runId) {
                await message.reply('❌ Please provide a run ID. Usage: `!botoff <run_id>`');
                return;
            }
 
            // Validate run ID format (alphanumeric string)
            if (!/^[a-zA-Z0-9\-_]+$/.test(runId) || runId.length < 3 || runId.length > 50) {
                await message.reply('❌ Invalid run ID format. Please provide a valid run ID from Terraform.');
                return;
            }
 
            // Check if this command is for this bot instance
            const currentRunId = this.bot.getRunId();
            if (runId !== currentRunId) {
                console.log(`Ignoring !botoff command for different bot instance. Current: ${currentRunId}, Requested: ${runId}`);
                return; // Silently ignore commands for other bot instances
            }
 
            // Disable the bot
            this.bot.disableBot(this.bot.getBotId());
 
            await message.reply(`🔴 **Bot Control Update**
🤖 **Run ID:** \`${runId}\`
🔴 **Status:** Disabled
👤 **Administrator:** ${message.author.tag}
 
⚠️ The bot will ignore all commands except \`!boton\` and \`!botoff\` until re-enabled.`);
 
            console.log(`🔴 Administrator ${message.author.tag} disabled bot ${runId}`);
 
        } catch (error) {
            console.error('Error handling botoff command:', error);
            await message.reply('❌ An error occurred while disabling the bot.');
        }
    }
}
 
module.exports = BotControlHandler;