add queue
Bi-Rabittoh andronacomarco@gmail.com
Mon, 08 Jan 2024 12:27:08 +0100
8 files changed,
221 insertions(+),
46 deletions(-)
A
commands/clear.js
@@ -0,0 +1,19 @@
+const { SlashCommandBuilder } = require('discord.js'); +const { getChannel, clearQueue } = require('../functions/music'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('clear') + .setDescription('Clear the queue.'), + + async execute(interaction) { + const channel = await getChannel(interaction); + if (typeof channel == 'string') + return await interaction.reply({ content: channel, ephemeral: true }); + + if (clearQueue()) + return await interaction.reply({ content: 'Queue cleared.' }); + + return await interaction.reply({ content: 'Error.' }); + }, +};
M
commands/outro.js
→
commands/outro.js
@@ -1,5 +1,5 @@
const { SlashCommandBuilder } = require('discord.js'); -const { playUrl, getChannel } = require('../functions/music'); +const { playOutro, getChannel } = require('../functions/music'); const path = require('node:path'); const { outros } = require(path.join(process.cwd(), 'config.json'));@@ -32,10 +32,9 @@
const outro = interaction.options.getString('which'); const kick = interaction.options.getString('kick'); const outroUrl = getOutroUrl(outro); - await playUrl(outroUrl, channel); + await playOutro(outroUrl, channel); - const kick_switch = kick ? kick : 'true'; - if (kick_switch == 'true') { + if (kick !== 'false') { setTimeout(() => interaction.member.voice.disconnect(), 20_000); return await interaction.reply({ content: 'Prepare for takeoff!', ephemeral: true }); }
M
commands/play.js
→
commands/play.js
@@ -1,8 +1,6 @@
const { SlashCommandBuilder } = require('discord.js'); const play = require('play-dl'); -const { playUrl, getChannel } = require('../functions/music'); - -// const reg = /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube(-nocookie)?\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/; +const { playUrls, getChannel } = require('../functions/music'); module.exports = { data: new SlashCommandBuilder()@@ -20,15 +18,13 @@ if (typeof channel == 'string')
return await interaction.reply({ content: channel, ephemeral: true }); await interaction.deferReply(); - - // Get the YouTube URL or search query const url = interaction.options.getString('query'); let video, yt_info; switch (play.yt_validate(url)) { case 'video': - video = { url: url }; - break; + playUrls([url], channel); + return await interaction.editReply(`Added ${url} to queue.`); case 'search': yt_info = await play.search(url, { source: { youtube: 'video' }, limit: 1 });@@ -37,12 +33,20 @@ if (yt_info.length === 0)
return await interaction.editReply('No results found.'); video = yt_info[0]; - break; + playUrls([video.url], channel); + return await interaction.editReply(`Added ${video.url} to queue.`); + case 'playlist': + const playlist = await play.playlist_info(url, { incomplete : true }); + const videos = await playlist.all_videos(); + const urls = videos.map((e) => e.url); + result = await playUrls(urls, channel); + if (result) + return await interaction.editReply(`Added ${urls.length} videos from the following playlist: ${playlist.title}.`); + else + return await interaction.editReply(`Could not add playlist.`); default: return await interaction.editReply('Not supported.'); } - playUrl(video.url, channel); - return await interaction.editReply(`Playing ${video.url}`); }, };
A
commands/queue.js
@@ -0,0 +1,25 @@
+const { SlashCommandBuilder } = require('discord.js'); +const { getChannel, getQueue } = require('../functions/music'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('queue') + .setDescription('Show current queue status.'), + + async execute(interaction) { + const channel = await getChannel(interaction); + if (typeof channel == 'string') + return await interaction.reply({ content: channel, ephemeral: true }); + + const result = await getQueue(); + if (result) { + let finalReply = "Now playing: " + result.shift() + "\n"; + + for (r in result) { + finalReply += "\n" + (r + 1) + ". " + result[r]; + } + return await interaction.reply({ content: finalReply }); + } + return await interaction.reply({ content: 'Queue is empty.' }); + }, +};
A
commands/skip.js
@@ -0,0 +1,18 @@
+const { SlashCommandBuilder } = require('discord.js'); +const { getChannel, skipMusic } = require('../functions/music'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('skip') + .setDescription('Skip current track.'), + + async execute(interaction) { + const channel = await getChannel(interaction); + if (typeof channel == 'string') + return await interaction.reply({ content: channel, ephemeral: true }); + + const result = await skipMusic(); + return await interaction.reply({ content: 'Skipped.' }); + //return await interaction.reply({ content: 'Error: couldn\'t skip.', ephemeral: true }); + }, +};
M
commands/stop.js
→
commands/stop.js
@@ -1,5 +1,5 @@
const { SlashCommandBuilder } = require('discord.js'); -const { getChannel } = require('../functions/music'); +const { getChannel, stopMusic } = require('../functions/music'); module.exports = { data: new SlashCommandBuilder()@@ -11,7 +11,9 @@ const channel = await getChannel(interaction);
if (typeof channel == 'string') return await interaction.reply({ content: channel, ephemeral: true }); - if (player) player.stop(); - return await interaction.reply({ content: 'Stopped.', ephemeral: true }); + if (stopMusic()) + return await interaction.reply({ content: 'Stopped.', ephemeral: true }); + + return await interaction.reply({ content: 'Error.', ephemeral: true }); }, };
M
functions/music.js
→
functions/music.js
@@ -1,51 +1,47 @@
const { createAudioResource, - createAudioPlayer, joinVoiceChannel, AudioPlayerStatus, } = require('@discordjs/voice'); const play = require('play-dl'); +const { MyQueue } = require('./myqueue') + +const q = new MyQueue(); + +function getChannelConnection(channel) { + const guild = channel.guild; + return joinVoiceChannel({ + channelId: channel.id, + guildId: guild.id, + adapterCreator: guild.voiceAdapterCreator + }) +} module.exports = { - async playUrl(url, channel) { + async playUrls(urls, channel) { if (!channel) { console.log('Channel error:', channel); return; } - const stream = await play.stream(url); - const guild = channel.guild; - const connection = joinVoiceChannel({ - channelId: channel.id, - guildId: guild.id, - adapterCreator: guild.voiceAdapterCreator, - }); - player = createAudioPlayer(); - player.on(AudioPlayerStatus.Idle, () => { - player.subscribers.forEach((element) => element.connection.disconnect()); - }); - - player.play(createAudioResource(stream.stream, { inputType: stream.type })); - connection.subscribe(player); + q.connection = getChannelConnection(channel); + return q.addArray(urls); }, async playStream(url, channel) { if (!channel) { console.log('Channel error:', channel); return; } - const guild = channel.guild; - const connection = joinVoiceChannel({ - channelId: channel.id, - guildId: guild.id, - adapterCreator: guild.voiceAdapterCreator, - }); - player = createAudioPlayer(); - player.on(AudioPlayerStatus.Idle, () => { - player.subscribers.forEach((element) => element.connection.disconnect()); - }); - - player.play(createAudioResource(url, { inputType: 'mp3' })); // 'opus', 'mp3' - connection.subscribe(player); + q.connection = getChannelConnection(channel); + q.add(createAudioResource(url, { inputType: 'mp3' })); + }, + async playOutro(url, channel) { + if (!channel) { + console.log('Channel error:', channel); + return; + } + q.connection = getChannelConnection(channel); + q.outro(url); }, async getChannel(interaction) { const member = interaction.member;@@ -58,4 +54,16 @@ return 'You\'re not in a voice channel.';
return channel; }, + async stopMusic() { + return q.stop(); + }, + async skipMusic() { + return q.next(); + }, + async getQueue() { + return q.queue; + }, + clearQueue() { + return q.clear(); + } };
A
functions/myqueue.js
@@ -0,0 +1,100 @@
+const { + createAudioResource, + createAudioPlayer, + AudioPlayerStatus +} = require('@discordjs/voice'); +const play = require('play-dl'); + +async function resourceFromUrl(url) { + const stream = await play.stream(url); + return createAudioResource(stream.stream, { inputType: stream.type }) +} + +class MyQueue { + #nowPlaying = null; + #queue = Array(); + get queue() { + if (this.#nowPlaying) + return [this.#nowPlaying].concat(this.#queue); + return null + } + + constructor() { + if (MyQueue._instance) { + return MyQueue._instance + } + MyQueue._instance = this; + + this.#nowPlaying = ""; + this.connection = null; + this.#queue = Array(); + this.player = createAudioPlayer(); + this.player.on(AudioPlayerStatus.Idle, () => { + if (this.#queue.length > 0) + return this.next(); + this.player.subscribers.forEach((e) => e.connection.disconnect()); + }); + } + + clear() { + this.#queue = Array(); + } + + stop() { + this.clear(); + this.#nowPlaying = null; + if (this.player) return this.player.stop(); + return false; + } + + async next() { + if (this.#queue.length == 0) + return this.stop(); + this.#nowPlaying = this.#queue.shift(); + const resource = await resourceFromUrl(this.#nowPlaying); + this.player.play(resource); + this.connection.subscribe(this.player); + } + + add(url, position=this.#queue.length) { + const l = this.#queue.length; + const normalizedPosition = position % (l + 1); + this.#queue.splice(normalizedPosition, 0, url); + if (l == 0) this.next(); + } + + async addArray(urls) { + const l = this.#queue.length; + this.#queue.push(...urls); + if (l == 0 && this.player.state.status == AudioPlayerStatus.Idle) this.next(); + return l != this.#queue.length; + } + + pause() { + this.player.pause(); + } + + resume() { + this.player.unpause(); + this.connection.subscribe(this.player); + } + + async outro(url) { + this.player.pause(); + const resource = await resourceFromUrl(url); + + const p = createAudioPlayer(); + p.on(AudioPlayerStatus.Idle, () => { + if (this.player.state.status == AudioPlayerStatus.Paused) { + this.resume(); + return + } + p.subscribers.forEach((e) => e.connection.disconnect()); + }); + + p.play(resource); + this.connection.subscribe(p); + } +} + +module.exports = { MyQueue }