All files / src DiscordReactionBot.js

98.38% Statements 61/62
50% Branches 1/2
95.45% Functions 21/22
98.38% Lines 61/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 143 1443x   3x 3x 3x 3x                     35x                       35x 35x 35x 35x 35x 35x 35x 35x 35x 35x     35x 35x 35x 35x     35x 35x             11x   11x     10x     10x       9x 9x 8x     3x 3x                   10x 10x 10x 10x 10x 10x 10x 10x 10x           2x 2x 2x 2x 2x 2x 2x 2x       1x 1x 1x 1x       5x       4x       6x       5x       3x             2x 2x 2x       3x
const { Client, GatewayIntentBits } = require('discord.js');
 
const ConfigurationResolver = require('./core/ConfigurationResolver');
const ComponentOrchestrator = require('./core/ComponentOrchestrator');
const BotLifecycleManager = require('./core/BotLifecycleManager');
const BotStateController = require('./core/BotStateController');
 
/**
 * DiscordReactionBot - Main bot coordinator class
 * Orchestrates specialized components for a clean, maintainable architecture
 * Now focused purely on coordination and high-level bot lifecycle management
 */
class DiscordReactionBot {
    constructor() {
        // Discord client with necessary intents for reaction roles and proposal system
        // These specific intents allow reading reactions, messages, and managing member roles
        this.client = new Client({
            intents: [
                GatewayIntentBits.Guilds,               // Access to guild info
                GatewayIntentBits.GuildMessages,        // Read messages for commands
                GatewayIntentBits.GuildMessageReactions, // Monitor reaction events
                GatewayIntentBits.MessageContent,       // Access message text for proposal parsing
                GatewayIntentBits.GuildMembers          // Role management capabilities
            ]
        });
 
        // Runtime configuration loaded from deployment
        // These values come from terraform and vary per deployment environment
        this.config = null;
        this.guildId = null;
        this.botToken = null;
        this.runId = null;
        this.moderatorRoleId = null;
        this.memberRoleId = null;
        this.commandChannelId = null;
        this.memberCommandChannelId = null;
        this.eventsTable = null;
        this.reminderIntervals = null;
        
        // Initialize specialized component managers
        this.configResolver = new ConfigurationResolver();
        this.componentOrchestrator = new ComponentOrchestrator(this);
        this.lifecycleManager = new BotLifecycleManager(this);
        this.stateController = new BotStateController(this.client);
        
        // Initialize components and setup event handlers
        this.componentOrchestrator.initializeComponents();
        this.lifecycleManager.setupEventHandlers();
    }
 
    /**
     * Initialize the bot with runtime configuration and start Discord connection
     */
    async initialize() {
        try {
            // Load and validate runtime configuration
            const runtimeConfig = await this.configResolver.loadConfiguration();
            
            // Extract configuration values
            this.extractConfigurationValues(runtimeConfig);
            
            // Initialize components that require configuration
            await this.componentOrchestrator.initializeConfigurableComponents(runtimeConfig);
 
            // Connect to Discord and start processing events
            // Bot becomes active and responsive after this point
            console.log('Logging into Discord...');
            await this.client.login(this.botToken);
            console.log('Bot initialized successfully');
            
        } catch (error) {
            console.error('Failed to initialize bot:', error);
            process.exit(1);
        }
    }
 
    /**
     * Extract configuration values from runtime config
     */
    extractConfigurationValues(runtimeConfig) {
        // Extract Discord-specific configuration values
        // These IDs are unique to each Discord server and deployment
        this.guildId = runtimeConfig.guildId;
        this.botToken = runtimeConfig.botToken;
        this.runId = runtimeConfig.runId || 'unknown';
        this.moderatorRoleId = runtimeConfig.moderatorRoleId;
        this.memberRoleId = runtimeConfig.memberRoleId;
        this.commandChannelId = runtimeConfig.commandChannelId;
        this.memberCommandChannelId = runtimeConfig.memberCommandChannelId;
        this.eventsTable = runtimeConfig.eventsTable;
        this.reminderIntervals = runtimeConfig.reminderIntervals;
    }
 
    // Getter methods for controlled access to bot configuration and components
    // These provide a clean interface for other modules to access bot state
    // without exposing the internal structure or allowing direct modification
    getGuildId() { return this.guildId; }
    getRunId() { return this.runId; }
    getModeratorRoleId() { return this.moderatorRoleId; }
    getMemberRoleId() { return this.memberRoleId; }
    getCommandChannelId() { return this.commandChannelId; }
    getMemberCommandChannelId() { return this.memberCommandChannelId; }
    getEventsTable() { return this.eventsTable; }
    getReminderIntervals() { return this.reminderIntervals; }
    
    // Component access methods
    getConfig() { return this.configManager.getConfig(); }
    getConfigManager() { return this.configManager; }
    getProposalManager() { return this.proposalManager; }
    getUserValidator() { return this.userValidator; }
    getEventManager() { return this.eventManager; }
 
    // Bot state management delegation
    enableBot(botId) { 
        return this.stateController.enableBot(botId); 
    }
    
    disableBot(botId) { 
        return this.stateController.disableBot(botId); 
    }
    
    isBotEnabled(botId) { 
        return this.stateController.isBotEnabled(botId); 
    }
    
    isThisBotEnabled() { 
        return this.stateController.isThisBotEnabled(); 
    }
    
    getBotId() { 
        return this.stateController.getBotId(); 
    }
 
    /**
     * Cleanup method for graceful shutdown
     */
    async cleanup() {
        await this.componentOrchestrator.cleanup();
        this.client.destroy();
        console.log('✅ DiscordReactionBot cleaned up');
    }
}
 
module.exports = DiscordReactionBot;