// Load environment variables from .env file require('dotenv').config(); const { Client, GatewayIntentBits, Collection } = require('discord.js'); const { Shoukaku, Connectors } = require('shoukaku'); const logger = require('./utils/logger'); const fs = require('fs'); const path = require('path'); // Validate essential environment variables if (!process.env.DISCORD_TOKEN) { logger.error('DISCORD_TOKEN is missing in the .env file!'); process.exit(1); } if (!process.env.LAVALINK_HOST || !process.env.LAVALINK_PORT || !process.env.LAVALINK_PASSWORD) { logger.warn('Lavalink connection details (HOST, PORT, PASSWORD) are missing or incomplete in .env. Music functionality will be limited.'); // Decide if the bot should exit or continue without music // process.exit(1); // Uncomment to exit if Lavalink is mandatory } // Create a new Discord client instance with necessary intents const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMessages, // Add if needed for prefix commands or message content GatewayIntentBits.MessageContent, // Add if needed for message content ], }); // Define Shoukaku nodes - fix the URL format to properly connect to Lavalink const Nodes = [ { name: 'lavalink', url: `${process.env.LAVALINK_HOST || 'localhost'}:${process.env.LAVALINK_PORT || '2333'}`, auth: process.env.LAVALINK_PASSWORD || 'youshallnotpass', secure: process.env.LAVALINK_SECURE === 'true' } ]; // Initialize Shoukaku with proper configuration client.shoukaku = new Shoukaku(new Connectors.DiscordJS(client), Nodes, { moveOnDisconnect: false, resume: true, reconnectTries: 10, reconnectInterval: 5000, }); // Show the actual Lavalink connection details (without exposing the actual password) logger.info(`Lavalink connection configured to: ${process.env.LAVALINK_HOST}:${process.env.LAVALINK_PORT} (Password: ${process.env.LAVALINK_PASSWORD ? '[SET]' : '[NOT SET]'})`); // Collections for commands client.commands = new Collection(); // --- Command Loading --- const commandsPath = path.join(__dirname, 'commands'); const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); for (const file of commandFiles) { const filePath = path.join(commandsPath, file); try { const command = require(filePath); // Set a new item in the Collection with the key as the command name and the value as the exported module if ('data' in command && 'execute' in command) { client.commands.set(command.data.name, command); logger.info(`Loaded command: ${command.data.name}`); } else { logger.warn(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`); } } catch (error) { logger.error(`Error loading command at ${filePath}: ${error.message}`, error); } } // --- Event Handling --- const eventsPath = path.join(__dirname, 'events'); const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js')); for (const file of eventFiles) { const filePath = path.join(eventsPath, file); const event = require(filePath); if (event.once) { client.once(event.name, (...args) => event.execute(...args, client)); // Pass client to event handlers logger.info(`Loaded event ${event.name} (once)`); } else { client.on(event.name, (...args) => event.execute(...args, client)); // Pass client to event handlers logger.info(`Loaded event ${event.name}`); } } // --- Shoukaku Event Handling --- // Set up Shoukaku event handlers client.shoukaku.on('ready', (name) => logger.info(`Lavalink Node: ${name} is now connected`)); client.shoukaku.on('error', (name, error) => logger.error(`Lavalink Node: ${name} emitted an error: ${error.message}`)); client.shoukaku.on('close', (name, code, reason) => logger.warn(`Lavalink Node: ${name} closed with code ${code}. Reason: ${reason || 'No reason'}`)); client.shoukaku.on('disconnect', (name, reason) => logger.warn(`Lavalink Node: ${name} disconnected. Reason: ${reason || 'No reason'}`)); // Log in to Discord with your client's token client.login(process.env.DISCORD_TOKEN) .then(() => logger.info('Successfully logged in to Discord.')) .catch(error => logger.error(`Failed to log in: ${error.message}`)); // Basic error handling process.on('unhandledRejection', error => { logger.error('Unhandled promise rejection:', error); }); process.on('uncaughtException', error => { logger.error('Uncaught exception:', error); // Optional: exit process on critical uncaught exceptions // process.exit(1); });