diff --git a/src/commands/join.js b/src/commands/join.js index 4e5ced3..987bab2 100644 --- a/src/commands/join.js +++ b/src/commands/join.js @@ -1,4 +1,4 @@ -const { SlashCommandBuilder, PermissionFlagsBits, ChannelType } = require('discord.js'); +const { SlashCommandBuilder, PermissionFlagsBits, ChannelType, MessageFlags } = require('discord.js'); // Import MessageFlags const logger = require('../utils/logger'); module.exports = { @@ -6,7 +6,8 @@ module.exports = { .setName('join') .setDescription('Joins your current voice channel'), async execute(interaction, client) { // Added client parameter - await interaction.deferReply({ ephemeral: true }); // Defer reply as joining might take time + // Use flags for ephemeral deferral + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); const member = interaction.member; const voiceChannel = member?.voice?.channel; @@ -29,44 +30,57 @@ module.exports = { return interaction.editReply('I can only join standard voice channels.'); } - // 3. Create or get the player and connect - let player = client.manager.get(interaction.guildId); + // Get the initialized Shoukaku player manager from the client object + const musicPlayer = interaction.client.player; + if (!musicPlayer) { + logger.error('Music player not initialized on client object!'); + return interaction.editReply('The music player is not ready yet. Please try again shortly.'); + } + + // 3. Get or create the player and connect using Shoukaku + let player = musicPlayer.getPlayer(interaction.guildId); if (!player) { try { - player = client.manager.create({ - guild: interaction.guildId, - voiceChannel: voiceChannel.id, - textChannel: interaction.channelId, // Store the channel where command was used - selfDeafen: true, // Automatically deafen the bot - // selfMute: false, // Bot starts unmuted + // Create player using the Shoukaku manager + player = await musicPlayer.createPlayer({ + guildId: interaction.guildId, + textChannel: interaction.channelId, + voiceChannel: voiceChannel.id }); - player.connect(); + // Connection is handled within createPlayer logger.info(`Created player and connected to voice channel ${voiceChannel.name} (${voiceChannel.id}) in guild ${interaction.guild.name} (${interaction.guildId})`); await interaction.editReply(`Joined ${voiceChannel.name}! Ready to play music.`); } catch (error) { logger.error(`Failed to create/connect player for guild ${interaction.guildId}: ${error.message}`, error); - // Try to destroy player if partially created - if (player) player.destroy(); + // Player destruction is handled internally if creation fails or via destroy method return interaction.editReply('An error occurred while trying to join the voice channel.'); } } else { - // If player exists but is not connected or in a different channel + // If player exists but is in a different channel if (player.voiceChannel !== voiceChannel.id) { - player.setVoiceChannel(voiceChannel.id); - if (!player.playing && !player.paused && !player.queue.size) { - player.connect(); // Connect if not already playing/paused/queued - } - logger.info(`Moved player to voice channel ${voiceChannel.name} (${voiceChannel.id}) in guild ${interaction.guildId}`); - await interaction.editReply(`Moved to ${voiceChannel.name}!`); + // Destroy the old player and create a new one in the correct channel + player.destroy(); + try { + player = await musicPlayer.createPlayer({ + guildId: interaction.guildId, + textChannel: interaction.channelId, + voiceChannel: voiceChannel.id + }); + logger.info(`Moved player to voice channel ${voiceChannel.name} (${voiceChannel.id}) in guild ${interaction.guildId}`); + await interaction.editReply(`Moved to ${voiceChannel.name}!`); + } catch (error) { + logger.error(`Failed to move player for guild ${interaction.guildId}: ${error.message}`, error); + return interaction.editReply('An error occurred while trying to move to the voice channel.'); + } } else { // Already in the correct channel await interaction.editReply(`I'm already in ${voiceChannel.name}!`); } - // Update text channel if needed + // Update text channel if needed (Shoukaku player object stores textChannel) if (player.textChannel !== interaction.channelId) { - player.setTextChannel(interaction.channelId); + player.textChannel = interaction.channelId; // Directly update the property logger.debug(`Updated player text channel to ${interaction.channel.name} (${interaction.channelId}) in guild ${interaction.guildId}`); } } diff --git a/src/commands/play.js b/src/commands/play.js index 48f1bca..9293ea2 100644 --- a/src/commands/play.js +++ b/src/commands/play.js @@ -1,6 +1,6 @@ const { SlashCommandBuilder, PermissionFlagsBits, ChannelType, EmbedBuilder } = require('discord.js'); const logger = require('../utils/logger'); -const { musicPlayer } = require('../structures/ShoukakuEvents'); +// Removed direct import of musicPlayer module.exports = { data: new SlashCommandBuilder() @@ -35,14 +35,21 @@ module.exports = { } try { + // Get the initialized player from the client object + const musicPlayer = interaction.client.player; + if (!musicPlayer) { + logger.error('Music player not initialized on client object!'); + return interaction.editReply('The music player is not ready yet. Please try again shortly.'); + } + // 3. Get or create player let player = musicPlayer.getPlayer(interaction.guildId); if (!player) { try { player = await musicPlayer.createPlayer({ guildId: interaction.guildId, - textChannel: interaction.channelId, - voiceChannel: voiceChannel.id + textChannel: interaction.channelId, // Use interaction.channelId directly + voiceChannel: voiceChannel.id // Use voiceChannel.id directly }); logger.info(`Created player and connected to voice channel ${voiceChannel.name} (${voiceChannel.id}) for play command.`); } catch (error) { @@ -61,7 +68,7 @@ module.exports = { } // 4. Search for tracks - const searchResults = await musicPlayer.search({ + const searchResults = await musicPlayer.search({ // Use the player instance from the client query: query, requester: interaction.user }); diff --git a/src/events/voiceStateUpdate.js b/src/events/voiceStateUpdate.js index 98bcb2d..9c3e5cc 100644 --- a/src/events/voiceStateUpdate.js +++ b/src/events/voiceStateUpdate.js @@ -4,46 +4,49 @@ const logger = require('../utils/logger'); module.exports = { name: Events.VoiceStateUpdate, execute(oldState, newState, client) { // Added client parameter - // Pass the event data to the Erela.js manager - // It handles the logic for joining/leaving channels, server muting/deafening, etc. - if (client.manager) { - try { - // Use newState primarily, as erela.js handles the diff internally - client.manager.voiceStateUpdate(newState); - // Optional: Add more specific logging if needed - // logger.debug(`Voice state update processed for user ${newState.member?.user?.tag || 'Unknown'} in guild ${newState.guild.id}`); - } catch (error) { - logger.error(`Error processing voice state update: ${error.message}`, error); - } - } else { - logger.warn('Voice state update received, but Erela.js manager is not initialized yet.'); + // Shoukaku handles voice state updates internally via its connector. + // We don't need to manually pass the update like with Erela.js. + // The warning about Erela.js manager not being initialized can be ignored/removed. + + // Custom logic for player cleanup based on voice state changes. + const musicPlayer = client.player; + if (!musicPlayer) { + // Player manager might not be ready yet, especially during startup. + // logger.debug('Voice state update received, but Shoukaku player manager is not ready yet.'); + return; } - // You can add custom logic here if needed, for example: - // - Check if the bot itself was disconnected and clean up the player. - // - Check if the channel the bot was in becomes empty. - const player = client.manager?.players.get(newState.guild.id); - if (!player) return; + const player = musicPlayer.getPlayer(newState.guild.id); + if (!player) return; // No active player for this guild - // Check if the bot was disconnected - if (newState.id === client.user.id && !newState.channelId && player) { - logger.info(`Bot was disconnected from voice channel in guild ${newState.guild.id}. Destroying player.`); - player.destroy(); + // Check if the bot was disconnected (newState has no channelId for the bot) + if (newState.id === client.user.id && !newState.channelId && oldState.channelId === player.voiceChannel) { + logger.info(`Bot was disconnected from voice channel ${oldState.channel?.name || oldState.channelId} in guild ${newState.guild.id}. Destroying player.`); + player.destroy(); // Use Shoukaku player's destroy method return; // Exit early as the player is destroyed } // Check if the bot's channel is now empty (excluding the bot itself) const channel = client.channels.cache.get(player.voiceChannel); - if (channel && channel.members.size === 1 && channel.members.has(client.user.id)) { - logger.info(`Voice channel ${channel.name} (${player.voiceChannel}) in guild ${newState.guild.id} is now empty (only bot left). Destroying player.`); - // Optional: Add a timeout before destroying - // setTimeout(() => { - // const currentChannel = client.channels.cache.get(player.voiceChannel); - // if (currentChannel && currentChannel.members.size === 1) { - // player.destroy(); - // } - // }, 60000); // e.g., 1 minute timeout - player.destroy(); + // Ensure the channel exists and the update is relevant to the bot's channel + if (channel && (newState.channelId === player.voiceChannel || oldState.channelId === player.voiceChannel)) { + // Fetch members again to ensure freshness after the update + const members = channel.members; + if (members.size === 1 && members.has(client.user.id)) { + logger.info(`Voice channel ${channel.name} (${player.voiceChannel}) in guild ${newState.guild.id} is now empty (only bot left). Destroying player.`); + // Optional: Add a timeout before destroying + // setTimeout(() => { + // const currentChannel = client.channels.cache.get(player.voiceChannel); + // const currentMembers = currentChannel?.members; + // if (currentMembers && currentMembers.size === 1 && currentMembers.has(client.user.id)) { + // logger.info(`Timeout finished: Destroying player in empty channel ${channel.name}.`); + // player.destroy(); + // } else { + // logger.info(`Timeout finished: Channel ${channel.name} is no longer empty. Player not destroyed.`); + // } + // }, 60000); // e.g., 1 minute timeout + player.destroy(); // Destroy immediately for now + } } }, };