diff --git a/src/commands/music/join.js b/src/commands/music/join.js new file mode 100644 index 0000000..d00283a --- /dev/null +++ b/src/commands/music/join.js @@ -0,0 +1,84 @@ +const { EmbedBuilder } = require("discord.js"); +const Queue = require("@src/database/schemas/Queue"); +const { MUSIC } = require("@root/config"); + +module.exports = { + name: "join", + description: "Join the voice channel and resume playback if there is a saved queue.", + category: "MUSIC", + validations: [], // Add any validations if necessary + command: { + enabled: true, + }, + slashCommand: { + enabled: true, + }, + + async messageRun(message, args) { + const response = await executeJoinCommand(message); + await message.safeReply(response); + }, + + async interactionRun(interaction) { + const response = await executeJoinCommand(interaction); + await interaction.followUp(response); + }, +}; + +async function executeJoinCommand(interaction) { + const { guild, member } = interaction; + const voiceChannel = member.voice.channel; + + if (!voiceChannel) { + return "You need to be in a voice channel to use this command."; + } + + // Get or create the player for the guild + let player = guild.client.musicManager.getPlayer(guild.id); + if (!player) { + player = await guild.client.musicManager.createPlayer({ + guildId: guild.id, + voiceChannelId: voiceChannel.id, + textChannelId: interaction.channel.id, + selfMute: false, + selfDeaf: true, + volume: MUSIC.DEFAULT_VOLUME, + }); + } + + // Check for saved queue in the database + const queueData = await Queue.findOne({ guildId: guild.id }); + if (queueData) { + // Restore the queue + player.queue.add(queueData.tracks.map(track => ({ + info: { + title: track.title, + uri: track.uri, + duration: track.duration, + }, + requester: { id: track.requester }, + }))); + + if (queueData.currentTrack) { + player.queue.current = { + info: { + title: queueData.currentTrack.title, + uri: queueData.currentTrack.uri, + duration: queueData.currentTrack.duration, + }, + requester: { id: queueData.currentTrack.requester }, + }; + + // Set the current position if available + player.position = queueData.currentTrack.position || 0; // Default to 0 if not set + } + + // Join the voice channel and play the current track + await player.connect(voiceChannel.id); + await player.play({ paused: false }); + + return `Joined ${voiceChannel.name} and resumed playback of **${player.queue.current.info.title}**.`; + } else { + return "No saved queue found."; + } +} \ No newline at end of file diff --git a/src/events/buttonInteraction.js b/src/events/buttonInteraction.js new file mode 100644 index 0000000..4c9d33f --- /dev/null +++ b/src/events/buttonInteraction.js @@ -0,0 +1,11 @@ +const joinCommand = require("@src/commands/music/join"); + +client.on("interactionCreate", async (interaction) => { + if (!interaction.isButton()) return; + + const { customId } = interaction; + + if (customId === "resume") { + await joinCommand.execute(interaction); + } +}); \ No newline at end of file diff --git a/src/events/player/playerDisconnect.js b/src/events/player/playerDisconnect.js index 71cfcb8..568714d 100644 --- a/src/events/player/playerDisconnect.js +++ b/src/events/player/playerDisconnect.js @@ -1,4 +1,5 @@ const Queue = require("@src/database/schemas/Queue"); +const { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } = require("discord.js"); module.exports = async (client, player) => { const guild = client.guilds.cache.get(player.guildId); @@ -18,6 +19,7 @@ module.exports = async (client, player) => { uri: player.queue.current.info.uri, duration: player.queue.current.info.duration, requester: player.queue.current.requester.id, + position: player.position || 0, } : null, }; @@ -35,4 +37,24 @@ module.exports = async (client, player) => { if (msg && msg.deletable) { await msg.delete().catch(() => {}); } + + // Send a message with a button to resume playback + const channel = guild.channels.cache.get(player.textChannelId); + if (channel) { + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId("resume") + .setLabel("Resume Music") + .setStyle(ButtonStyle.Primary) + ); + + await channel.safeSend({ + embeds: [ + new EmbedBuilder() + .setColor(EMBED_COLORS.BOT_EMBED) + .setDescription("The music has stopped due to a disconnection. You can resume it by clicking the button below.") + ], + components: [row] + }); + } }; \ No newline at end of file diff --git a/src/events/player/trackEnd.js b/src/events/player/trackEnd.js index 6ace2e3..b9363a4 100644 --- a/src/events/player/trackEnd.js +++ b/src/events/player/trackEnd.js @@ -17,7 +17,7 @@ module.exports = async (client, player, track) => { // Check if the current track was saved in the database const queueData = await Queue.findOne({ guildId: player.guildId }); - + // Remove the current track from the database after it has been played if (queueData) { const updatedTracks = queueData.tracks.filter(t => t.uri !== track.info.uri); @@ -25,6 +25,11 @@ module.exports = async (client, player, track) => { { guildId: player.guildId }, { tracks: updatedTracks, currentTrack: null } ); + + // If there are no more tracks left, consider cleaning up the database entry + if (updatedTracks.length === 0) { + await Queue.deleteOne({ guildId: player.guildId }); + } } if (player.get("autoplay") === true) { diff --git a/src/events/player/trackStart.js b/src/events/player/trackStart.js index be54533..d46ba1c 100644 --- a/src/events/player/trackStart.js +++ b/src/events/player/trackStart.js @@ -1,6 +1,7 @@ const { EmbedBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder } = require("discord.js"); const { EMBED_COLORS } = require("@root/config"); const Queue = require("@src/database/schemas/Queue"); +const joinCommand = require("@src/commands/music/join"); module.exports = async (client, player, track) => { const guild = client.guilds.cache.get(player.guildId); @@ -28,12 +29,15 @@ module.exports = async (client, player, track) => { }, requester: { id: queueData.currentTrack.requester }, }; - } - // Clear the database entry after restoring - await Queue.deleteOne({ guildId: player.guildId }); + // Set the current position if available + player.position = queueData.currentTrack.position || 0; // Default to 0 if not set + } } + // Save the current track to the database + await saveCurrentTrackToDatabase(player, track); + if (!player.textChannelId || !track) return; const channel = guild.channels.cache.get(player.textChannelId); @@ -44,7 +48,7 @@ module.exports = async (client, player, track) => { } if (player.voiceChannelId) { - await client.utils.setVoiceStatus(client, player.voiceChannelId, `Paying: **${track.info.title}**`); + await client.utils.setVoiceStatus(client, player.voiceChannelId, `Playing: **${track.info.title}**`); } const previous = await player.queue.shiftPrevious(); @@ -239,4 +243,24 @@ module.exports = async (client, player, track) => { embeds: [new EmbedBuilder().setDescription(description).setColor(EMBED_COLORS.BOT_EMBED)], }); }); -}; \ No newline at end of file +}; + +// Function to save the current track to the database +async function saveCurrentTrackToDatabase(player, track) { + const queueData = { + guildId: player.guildId, + currentTrack: { + title: track.info.title, + uri: track.info.uri, + duration: track.info.duration, + requester: track.requester.id, + position: player.position || 0, // Save the current position + }, + }; + + await Queue.findOneAndUpdate( + { guildId: player.guildId }, + queueData, + { upsert: true, new: true } + ); +} \ No newline at end of file