diff --git a/commands/loop.js b/commands/loop.js new file mode 100644 index 0000000..abd3299 --- /dev/null +++ b/commands/loop.js @@ -0,0 +1,17 @@ +const musicQueue = require('../musicQueue'); + +async function loopCommand(interaction) { + await interaction.deferReply(); + + const looping = interaction.options.getBoolean('looping'); + + musicQueue.setLooping(interaction.guild.id, looping); + + if (looping) { + interaction.followUp('Enabled looping for the current queue.'); + } else { + interaction.followUp('Disabled looping for the current queue.'); + } +} + +module.exports.loopCommand = loopCommand; \ No newline at end of file diff --git a/commands/pause.js b/commands/pause.js new file mode 100644 index 0000000..dea87ef --- /dev/null +++ b/commands/pause.js @@ -0,0 +1,32 @@ +const { musicPlayer } = require('../utils/musicPlayer'); +const { AudioPlayerStatus, joinVoiceChannel, AudioPlayerState } = require('@discordjs/voice'); + +async function pauseCommand(interaction) { + await interaction.deferReply(); + + const voiceChannel = interaction.member.voice.channel; + + const connection = joinVoiceChannel({ + channelId: voiceChannel.id, + guildId: interaction.guild.id, + adapterCreator: interaction.guild.voiceAdapterCreator, + selfDeaf: false, + selfMute: false + }); + + let player = await musicPlayer(interaction.guild.id, connection, false); + + if (!voiceChannel) { + return interaction.followUp('You must be in a voice channel to use this command.'); + } + + if (!player) { + return interaction.followUp('I am not currently playing music in a voice channel.'); + } + +// player.pause(); + + interaction.followUp('Paused the music.'); +} + +module.exports.pauseCommand = pauseCommand; \ No newline at end of file diff --git a/commands/play.js b/commands/play.js new file mode 100644 index 0000000..8d917a9 --- /dev/null +++ b/commands/play.js @@ -0,0 +1,35 @@ +const { getMusicStream } = require('./../utils/getMusicStream'); +const musicQueue = require('../musicQueue'); +const { musicPlayer } = require('../utils/musicPlayer'); + +const { + joinVoiceChannel, +} = require('@discordjs/voice'); + +async function playCommand(interaction) { + await interaction.deferReply(); + + const query = interaction.options.getString('input'); + const voiceChannel = interaction.member.voice.channel; + + if (!voiceChannel) { + return interaction.followUp('You must be in a voice channel to use this command.'); + } + const song = await getMusicStream(query); + + musicQueue.addToQueue(interaction.guild.id, song); + + const connection = joinVoiceChannel({ + channelId: voiceChannel.id, + guildId: interaction.guild.id, + adapterCreator: interaction.guild.voiceAdapterCreator, + selfDeaf: false, + selfMute: false + }); + + musicPlayer(interaction.guild.id, connection); + + interaction.followUp(`Added ${song.title} to the queue.`); +} + +module.exports.playCommand = playCommand; \ No newline at end of file diff --git a/commands/resume.js b/commands/resume.js new file mode 100644 index 0000000..09d51d2 --- /dev/null +++ b/commands/resume.js @@ -0,0 +1,32 @@ +const { musicPlayer } = require('../utils/musicPlayer'); +const { joinVoiceChannel } = require('@discordjs/voice'); + +async function resumeCommand(interaction) { + await interaction.deferReply(); + + const voiceChannel = interaction.member.voice.channel; + + const connection = joinVoiceChannel({ + channelId: voiceChannel.id, + guildId: interaction.guild.id, + adapterCreator: interaction.guild.voiceAdapterCreator, + selfDeaf: false, + selfMute: false + }); + + let player = await musicPlayer(interaction.guild.id, connection, false); + + if (!voiceChannel) { + return interaction.followUp('You must be in a voice channel to use this command.'); + } + + if (!player) { + return interaction.followUp('I am not currently playing music in a voice channel.'); + } + + player.unpause(); + + interaction.followUp('Resumed the music.'); +} + +module.exports.resumeCommand = resumeCommand; \ No newline at end of file diff --git a/index.js b/index.js index d2d1595..b3053e6 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,12 @@ -const { playCommand } = require('./play.js'); const { Client, GatewayIntentBits } = require('discord.js'); -const { queueCommand } = require('./commands/queue'); const { registerCommands } = require('./utils/registerCommands'); -const process = require('dotenv').config(); +const { playCommand } = require('./commands/play'); +const { queueCommand } = require('./commands/queue'); +const { pauseCommand } = require('./commands/pause'); +const { resumeCommand } = require('./commands/resume'); +const { loopCommand } = require('./commands/loop'); +const process = require('dotenv').config(); const clientId = process.parsed.clientId; const token = process.parsed.token; @@ -31,8 +34,14 @@ client.on('interactionCreate', async (interaction) => { await playCommand(interaction); } else if (commandName === 'queue') { await queueCommand(interaction); + } else if (commandName === 'pause') { + await pauseCommand(interaction); + } else if (commandName === 'resume') { + await resumeCommand(interaction); + } else if (commandName === 'loop') { + await loopCommand(interaction); } -}); + }); // client.login(process.env.TOKEN); diff --git a/musicQueue.js b/musicQueue.js index 030816f..0876d2a 100644 --- a/musicQueue.js +++ b/musicQueue.js @@ -1,24 +1,42 @@ class MusicQueue { - constructor() { - this.queue = new Map(); + constructor() { + this.queue = new Map(); + this.looping = new Map(); + } + + addToQueue(guildId, song) { + if (!this.queue.has(guildId)) { + this.queue.set(guildId, []); } - - getQueue(guildId) { - if (!this.queue.has(guildId)) { - this.queue.set(guildId, []); - } - return this.queue.get(guildId); + + this.queue.get(guildId).push(song); + } + + removeFromQueue(guildId) { + if (!this.queue.has(guildId)) { + return; } - - addToQueue(guildId, song) { - const serverQueue = this.getQueue(guildId); - serverQueue.push(song); - } - - removeFromQueue(guildId) { - const serverQueue = this.getQueue(guildId); + + const serverQueue = this.queue.get(guildId); + + if (this.looping.has(guildId) && this.looping.get(guildId)) { + serverQueue.push(serverQueue.shift()); + } else { serverQueue.shift(); } } - - module.exports = new MusicQueue(); \ No newline at end of file + + getQueue(guildId) { + if (!this.queue.has(guildId)) { + return []; + } + + return this.queue.get(guildId); + } + + setLooping(guildId, looping) { + this.looping.set(guildId, looping); + } +} + +module.exports = new MusicQueue(); \ No newline at end of file diff --git a/utils/getMusicStream.js b/utils/getMusicStream.js index a1ca5a4..a7458a7 100644 --- a/utils/getMusicStream.js +++ b/utils/getMusicStream.js @@ -24,10 +24,10 @@ async function getMusicStream(query) { type = StreamType.OggOpus; } else { - stream = await youtube.getStream(query); - songTitle = stream.title ?? 'Unknown'; - songDuration = stream.duration ?? 'Unknown'; - stream = stream.stream; + stream = await youtube.getStream(query) + songTitle = stream?.title ?? 'Unknown'; + songDuration = stream?.duration ?? 'Unknown'; + stream = stream?.stream; type = StreamType.Opus; } diff --git a/play.js b/utils/musicPlayer.js similarity index 57% rename from play.js rename to utils/musicPlayer.js index a611cdb..2db75fd 100644 --- a/play.js +++ b/utils/musicPlayer.js @@ -1,40 +1,12 @@ const { - joinVoiceChannel, createAudioResource, createAudioPlayer, NoSubscriberBehavior, AudioPlayerStatus, } = require('@discordjs/voice'); -const musicQueue = require('./musicQueue'); -const { getMusicStream } = require('./utils/getMusicStream'); - -async function playCommand(interaction) { - await interaction.deferReply(); +const musicQueue = require('../musicQueue'); - const query = interaction.options.getString('input'); - const voiceChannel = interaction.member.voice.channel; - - if (!voiceChannel) { - return interaction.followUp('You must be in a voice channel to use this command.'); - } - const song = await getMusicStream(query); - - musicQueue.addToQueue(interaction.guild.id, song); - - const connection = joinVoiceChannel({ - channelId: voiceChannel.id, - guildId: interaction.guild.id, - adapterCreator: interaction.guild.voiceAdapterCreator, - selfDeaf: false, - selfMute: false - }); - - playSong(interaction.guild.id, connection); - - interaction.followUp(`Added ${song.title} to the queue.`); - } - - async function playSong(guildId, connection) { +async function musicPlayer(guildId, connection) { const serverQueue = musicQueue.getQueue(guildId); if (serverQueue.length === 0) { @@ -44,6 +16,12 @@ async function playCommand(interaction) { const song = serverQueue[0]; + if(song.stream == null){ + musicQueue.removeFromQueue(guildId); + musicPlayer(guildId, connection); + return; + } + let resource = createAudioResource(song.stream, { inputType: song.type }) @@ -54,6 +32,7 @@ async function playCommand(interaction) { } }) + player.play(resource) connection.subscribe(player) @@ -61,8 +40,10 @@ async function playCommand(interaction) { player.on(AudioPlayerStatus.Idle, () => { console.log('Song ended:', song.title); musicQueue.removeFromQueue(guildId); - playSong(guildId, connection); + musicPlayer(guildId, connection); }); + + return player; } // TODO: USE THIS AGAIN @@ -83,4 +64,4 @@ function convertToMilliseconds(songLenth) { } } -module.exports.playCommand = playCommand; \ No newline at end of file +module.exports.musicPlayer = musicPlayer; \ No newline at end of file diff --git a/utils/registerCommands.js b/utils/registerCommands.js index 1423cea..6efa76a 100644 --- a/utils/registerCommands.js +++ b/utils/registerCommands.js @@ -19,7 +19,21 @@ async function registerCommands(clientId, token) { option.setName('song') .setDescription('Add song from YouTube, Spotify, SoundCloud, etc. to the queue') .setRequired(true) - ) + ), + new SlashCommandBuilder() + .setName('pause') + .setDescription('Pauses the current song!'), + new SlashCommandBuilder() + .setName('resume') + .setDescription('Resumes the current song!'), + new SlashCommandBuilder() + .setName('loop') + .setDescription('Loops the current song!') + .addBooleanOption(option => + option.setName('looping') + .setDescription('Enable or disable looping') + .setRequired(true) + ) ]; const rest = new REST({ version: '9' }).setToken(token);