Add stop functionality, fix duration

This commit is contained in:
Azaaxin
2023-06-25 11:08:14 +02:00
committed by Myx
parent dddbe81fab
commit c802be1a61
17 changed files with 304 additions and 226 deletions

View File

@@ -1,9 +1,6 @@
const play = require('play-dl'); const play = require('play-dl');
async function ReAuth() { async function ReAuth() {
/*if (play.is_expired()) {
await play.refreshToken()
}*/
play.getFreeClientID().then((clientID) => { play.getFreeClientID().then((clientID) => {
play.setToken({ play.setToken({

View File

@@ -1,32 +1,34 @@
const musicQueue = require('../musicQueue'); const musicQueue = require("../musicQueue");
async function enableLooping(interaction) { async function enableLooping(interaction) {
await interaction.deferReply(); await interaction.deferReply();
const guildId = interaction.guild.id; const guildId = interaction.guild.id;
musicQueue.enableLooping(guildId); musicQueue.enableLooping(guildId);
interaction.followUp('Enabled looping for the current queue.'); interaction.followUp("Enabled looping for the current queue.");
} }
async function unloopCommand(interaction) { async function unloopCommand(interaction) {
await interaction.deferReply(); await interaction.deferReply();
const guildId = interaction.guild.id; const guildId = interaction.guild.id;
musicQueue.disableLooping(guildId); musicQueue.disableLooping(guildId);
interaction.followUp('Disabled looping for the current queue.'); interaction.followUp("Disabled looping for the current queue.");
} }
async function toggleLoopCommand(interaction) { async function toggleLoopCommand(interaction) {
await interaction.deferReply(); await interaction.deferReply();
const guildId = interaction.guild.id; const guildId = interaction.guild.id;
if (musicQueue.looping.has(guildId) && musicQueue.looping.get(guildId)) {
musicQueue.disableLooping(guildId, false); if (musicQueue.looping.has(guildId) && musicQueue.looping.get(guildId)) {
interaction.followUp('Disabled looping for the current queue.'); musicQueue.disableLooping(guildId, false);
} else { interaction.followUp("Disabled looping for the current queue.");
musicQueue.enableLooping(guildId, true); } else {
interaction.followUp('Enabled looping for the current queue.'); musicQueue.enableLooping(guildId, true);
} interaction.followUp("Enabled looping for the current queue.");
}
} }
module.exports.toggleLoopCommand = toggleLoopCommand; module.exports.toggleLoopCommand = toggleLoopCommand;
module.exports.unloopCommand = unloopCommand; module.exports.unloopCommand = unloopCommand;
module.exports.enableLooping = enableLooping; module.exports.enableLooping = enableLooping;

View File

@@ -1,32 +0,0 @@
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;

View File

@@ -1,23 +1,33 @@
const { getVoiceConnection } = require('@discordjs/voice'); const { getVoiceConnection } = require("@discordjs/voice");
async function pauseCommand(interaction) { async function pauseCommand(interaction) {
await interaction.deferReply(); await interaction.deferReply();
const connection = getVoiceConnection(interaction.guild.id); const connection = getVoiceConnection(interaction.guild.id);
if (!connection) { if (!connection) {
return interaction.followUp('There is no active music player in this server.'); return interaction.followUp(
"There is no active music player in this server."
);
} }
connection.state.subscription.player.pause(); connection.state.subscription.player.pause();
interaction.followUp('Paused the music.'); interaction.followUp("Paused the music.");
} }
async function unpauseCommand(interaction) { async function unpauseCommand(interaction) {
await interaction.deferReply(); await interaction.deferReply();
const connection = getVoiceConnection(interaction.guild.id); const connection = getVoiceConnection(interaction.guild.id);
if (!connection) { if (!connection) {
return interaction.followUp('There is no active music player in this server.'); return interaction.followUp(
"There is no active music player in this server."
);
} }
connection.state.subscription.player.unpause(); connection.state.subscription.player.unpause();
interaction.followUp('Unpaused the music.'); interaction.followUp("Resumed the music.");
} }
module.exports.pauseCommand = pauseCommand; module.exports.pauseCommand = pauseCommand;

View File

@@ -1,33 +1,32 @@
const { getMusicStream } = require('./../utils/getMusicStream'); const { getMusicStream } = require("./../utils/getMusicStream");
const musicQueue = require('../musicQueue'); const musicQueue = require("../musicQueue");
const { musicPlayer } = require('../utils/musicPlayer'); const { musicPlayer } = require("../utils/musicPlayer");
const { joinVoiceChannel } = require("@discordjs/voice");
const {
joinVoiceChannel,
} = require('@discordjs/voice');
async function playCommand(interaction) { async function playCommand(interaction) {
await interaction.deferReply(); await interaction.deferReply();
const query = interaction.options.getString('input'); const query = interaction.options.getString("input");
const voiceChannel = interaction.member.voice.channel; const voiceChannel = interaction.member.voice.channel;
if (!voiceChannel) { if (!voiceChannel) {
return interaction.followUp('You must be in a voice channel to use this command.'); return interaction.followUp(
} "You must be in a voice channel to use this command."
const song = await getMusicStream(query); );
}
const song = await getMusicStream(query);
musicQueue.addToQueue(interaction.guild.id, song); musicQueue.addToQueue(interaction.guild.id, song);
const connection = joinVoiceChannel({ const connection = joinVoiceChannel({
channelId: voiceChannel.id, channelId: voiceChannel.id,
guildId: interaction.guild.id, guildId: interaction.guild.id,
adapterCreator: interaction.guild.voiceAdapterCreator, adapterCreator: interaction.guild.voiceAdapterCreator,
selfDeaf: false, selfDeaf: false,
selfMute: false selfMute: false,
}); });
musicPlayer(interaction.guild.id, connection, interaction); musicPlayer(interaction.guild.id, connection, interaction);
} }
module.exports.playCommand = playCommand; module.exports.playCommand = playCommand;

View File

@@ -1,24 +1,27 @@
const musicQueue = require('../musicQueue'); const musicQueue = require("../musicQueue");
const { getMusicStream } = require('../utils/getMusicStream'); const { getMusicStream } = require("../utils/getMusicStream");
async function queueCommand(interaction) { async function queueCommand(interaction) {
await interaction.deferReply(); await interaction.deferReply();
const query = interaction.options.getString('song'); const query = interaction.options.getString("song");
const voiceChannel = interaction.member.voice.channel; const voiceChannel = interaction.member.voice.channel;
if (!voiceChannel) { if (!voiceChannel) {
return interaction.followUp('You must be in a voice channel to use this command.'); return interaction.followUp(
} "You must be in a voice channel to use this command."
);
}
const song = await getMusicStream(query); const song = await getMusicStream(query);
if (!song) { if (!song) {
return interaction.followUp('Error finding song. Try Again.').then(msg => setTimeout(() => msg.delete(), 10000)); return interaction
} .followUp("Error finding song. Try Again.")
.then((msg) => setTimeout(() => msg.delete(), 10000));
}
musicQueue.addToQueue(interaction.guild.id, song); musicQueue.addToQueue(interaction.guild.id, song);
interaction.followUp(`Added ${song.title} to the queue.`);
interaction.followUp(`Added ${song.title} to the queue.`);
} }
module.exports.queueCommand = queueCommand; module.exports.queueCommand = queueCommand;

View File

@@ -1,32 +0,0 @@
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;

30
commands/stop.js Normal file
View File

@@ -0,0 +1,30 @@
const musicQueue = require("../musicQueue");
const { getVoiceConnection } = require("@discordjs/voice");
async function stopCommand(interaction) {
await interaction.deferReply();
const voiceChannel = interaction.member.voice.channel;
const connection = getVoiceConnection(interaction.guild.id);
if (!voiceChannel) {
return interaction.followUp(
"You must be in a voice channel to use this command."
);
}
const guildId = interaction.guild.id;
if (!connection.state.subscription.player) {
return interaction.followUp(
"I am not currently playing music in a voice channel."
);
}
connection.state.subscription.player.stop();
musicQueue.clearQueue(guildId);
interaction.followUp("Stopped the music and cleared the queue.");
}
module.exports.stopCommand = stopCommand;

View File

@@ -2,7 +2,8 @@ const { Client, GatewayIntentBits } = require('discord.js');
const { registerCommands } = require('./utils/registerCommands'); const { registerCommands } = require('./utils/registerCommands');
const { playCommand } = require('./commands/play'); const { playCommand } = require('./commands/play');
const { queueCommand } = require('./commands/queue'); const { queueCommand } = require('./commands/queue');
const { pauseCommand, unpauseCommand } = require('./commands/pause_resume'); const { stopCommand } = require('./commands/stop');
const { pauseCommand, unpauseCommand } = require('./commands/pauseResume');
const { toggleLoopCommand } = require('./commands/loop'); const { toggleLoopCommand } = require('./commands/loop');
const { ReAuth } = require('./ReAuthenticate'); const { ReAuth } = require('./ReAuthenticate');
@@ -11,13 +12,14 @@ const clientId = process.parsed.clientId;
const token = process.parsed.token; const token = process.parsed.token;
const client = new Client({ const client = new Client({
intents: [ intents: [
GatewayIntentBits.Guilds, GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent, GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildVoiceStates,
GatewayIntentBits.GuildIntegrations GatewayIntentBits.GuildIntegrations
] }) ]
})
client.on('ready', async () => { client.on('ready', async () => {
console.log(`Logged in as ${client.user.tag}!`); console.log(`Logged in as ${client.user.tag}!`);
@@ -26,27 +28,29 @@ client.on('ready', async () => {
}); });
client.on('interactionCreate', async (interaction) => { client.on('interactionCreate', async (interaction) => {
if (!interaction.isCommand()) return; if (!interaction.isCommand()) return;
const { commandName } = interaction; const { commandName } = interaction;
if (commandName === 'play') { if (commandName === 'play') {
await playCommand(interaction); await playCommand(interaction);
} else if (commandName === 'queue') { } else if (commandName === 'queue') {
await queueCommand(interaction); await queueCommand(interaction);
} else if (commandName === 'pause') { } else if (commandName === 'pause') {
await pauseCommand(interaction); await pauseCommand(interaction);
} else if (commandName === 'resume') { } else if (commandName === 'resume') {
await unpauseCommand(interaction); await unpauseCommand(interaction);
} else if (commandName === 'loop') { } else if (commandName === 'loop') {
await toggleLoopCommand(interaction); await toggleLoopCommand(interaction);
} } else if (commandName === 'stop') {
}); await stopCommand(interaction);
}
});
client.on('messageCreate', async (message) => { client.on('messageCreate', async (message) => {
if(message.content == 'reauth') { if(message.content == 'reauth') {
await ReAuth(); await ReAuth();
} }
}); });
client.login(token); client.login(token);

View File

@@ -45,6 +45,14 @@ class MusicQueue {
disableLooping(guildId) { disableLooping(guildId) {
this.looping.set(guildId, false); this.looping.set(guildId, false);
} }
clearQueue(guildId) {
if (!this.queue.has(guildId)) {
return;
}
this.queue.set(guildId, []);
}
} }
module.exports = new MusicQueue(); module.exports = new MusicQueue();

View File

@@ -1,14 +1,14 @@
const playdl = require('play-dl'); const playdl = require("play-dl");
async function getStream(query) { async function getStream(query) {
let songInformation = await playdl.soundcloud(query) // Make sure that url is track url only. For playlist, make some logic. let songInformation = await playdl.soundcloud(query);
let stream = await playdl.stream_from_info(songInformation, { quality: 2 }); let stream = await playdl.stream_from_info(songInformation, { quality: 2 });
return { return {
title: songInformation.name, title: songInformation.name,
stream: stream.stream, stream: stream.stream,
duration: songInformation.durationInSec / 1000, duration: songInformation.durationInSec * 1000,
userInput: query userInput: query,
} };
} }
module.exports.getStream = getStream; module.exports.getStream = getStream;

View File

@@ -1,14 +1,13 @@
const playdl = require('play-dl'); const playdl = require('play-dl');
//TODO ADD SPOTIFY SUPPORT
async function getStream(query) { async function getStream(query) {
const trackInfo = await playdl.spotify(query); const trackInfo = await playdl.spotify(query);
let searched = await play.search(`${trackInfo.name}`, { let searched = await play.search(`${trackInfo.name}`, {
limit: 1 limit: 1
}) // This will search the found track on youtube. })
let stream = await play.stream(searched[0].url) // This will create stream from the above search
let stream = await play.stream(searched[0].url)
return stream; return stream;
} }

View File

@@ -1,5 +1,5 @@
const ytsr = require('ytsr'); const ytsr = require("ytsr");
const playdl = require('play-dl'); const playdl = require("play-dl");
async function getStream(query) { async function getStream(query) {
try { try {
@@ -7,29 +7,48 @@ async function getStream(query) {
const match = query.match(regex); const match = query.match(regex);
let videoId; let videoId;
let usingYtsr = false; let usingYtsr = false;
if(match == null) { if (match == null) {
let result = await playdl.search(query, { limit: 1}); let result = await playdl.search(query, { limit: 1 });
videoId = result[0].id; videoId = result[0].id;
if (videoId == null) { if (videoId == null) {
usingYtsr = true; usingYtsr = true;
const searchResults = await ytsr(query, { page: 1, type: 'video' }); const searchResults = await ytsr(query, {
page: 1,
type: "video",
});
videoId = searchResults.items[0].id; videoId = searchResults.items[0].id;
} }
} else { } else {
videoId = match[1]; videoId = match[1];
} }
const streamResult = await playdl.stream(`https://www.youtube.com/watch?v=${videoId}`, { quality: 2 }); const streamResult = await playdl.stream(
const infoResult = usingYtsr ? await ytsr(`https://www.youtube.com/watch?v=${videoId}`, { limit: 1}) : await playdl.video_info(`https://www.youtube.com/watch?v=${videoId}`); `https://www.youtube.com/watch?v=${videoId}`,
console.log(infoResult) { quality: 2 }
console.log("\x1b[36m",' Id: ', videoId, 'Alternative search:', usingYtsr) );
const infoResult = usingYtsr
? await ytsr(`https://www.youtube.com/watch?v=${videoId}`, {
limit: 1,
})
: await playdl.video_info(
`https://www.youtube.com/watch?v=${videoId}`
);
console.log("\x1b[36m"," Id: ", videoId, "Alternative search:", usingYtsr);
return { return {
title: (usingYtsr ? infoResult.items[0].title : infoResult.video_details.title) ?? 'Unknown, error fetching title.', title:
duration: (usingYtsr ? infoResult.items[0].duration : infoResult.video_details.durationInSec) ?? 'Unknown, error fetching duration.', (usingYtsr
? infoResult.items[0].title
: infoResult.video_details.title) ??
0,
duration:
(usingYtsr
? infoResult.items[0].duration
: (infoResult.video_details.durationInSec * 1000)) ??
0,
stream: streamResult.stream, stream: streamResult.stream,
type: streamResult.type, type: streamResult.type,
userInput: query userInput: query,
}; };
} catch (error) { } catch (error) {
console.log("\x1b[31m", error); console.log("\x1b[31m", error);

View File

@@ -35,3 +35,4 @@
"ytsr": "^3.8.2" "ytsr": "^3.8.2"
} }
} }

View File

@@ -4,19 +4,22 @@ const {
NoSubscriberBehavior, NoSubscriberBehavior,
AudioPlayerStatus, AudioPlayerStatus,
} = require('@discordjs/voice'); } = require('@discordjs/voice');
const { EmbedBuilder } = require('discord.js');
const musicQueue = require('../musicQueue'); const musicQueue = require('../musicQueue');
const { progressBar } = require('../utils/progress');
async function musicPlayer(guildId, connection, interaction) { async function musicPlayer(guildId, connection, interaction) {
const serverQueue = musicQueue.getQueue(guildId); const serverQueue = musicQueue.getQueue(guildId);
if (serverQueue.length === 0) { if (serverQueue.length === 0) {
connection.destroy(); connection.destroy();
return; return;
} }
const song = serverQueue[0]; const song = serverQueue[0];
if(song.stream == null){ if (song.stream == null) {
musicQueue.removeFromQueue(guildId); musicQueue.removeFromQueue(guildId);
musicPlayer(guildId, connection); musicPlayer(guildId, connection);
return; return;
@@ -35,14 +38,34 @@ async function musicPlayer(guildId, connection, interaction) {
player.play(resource) player.play(resource)
connection.subscribe(player) connection.subscribe(player)
interaction.followUp(`Added **${song.title}** to the queue.`).then(message =>
setTimeout(() => progressBar(0, 0, true)
message.delete(),
song.duration + 10000)); if(interaction.commandName == "play") {
interaction.followUp(`~🎵~`).then(message => {
const embed = new EmbedBuilder()
.setColor("#E0B0FF")
.setTitle("Now playing: " + song.title)
.setDescription(progressBar(song.duration, 10).progressBarString);
message.edit({ embeds: [embed] });
inter = setInterval(() => {
const { progressBarString, isDone } = progressBar(song.duration, 10);
if (isDone) {
clearInterval(inter);
message.delete();
}
embed.setDescription(progressBarString);
message.edit({ embeds: [embed] });
}, 2000)
});
}
player.on(AudioPlayerStatus.Idle, async () => { player.on(AudioPlayerStatus.Idle, async () => {
console.log('Song ended:', song.title); console.log('Song ended:', song.title);
await musicQueue.removeFromQueue(guildId) await musicQueue.removeFromQueue(guildId)
musicPlayer(guildId, connection, interaction);
}); });
return player; return player;

38
utils/progress.js Normal file
View File

@@ -0,0 +1,38 @@
let startTime;
let current = 0;
let percentage;
const progressBar = (totalInMilliseconds, size, reset = false) => {
if (reset) {
startTime = Date.now();
current = 0;
}
if (!startTime) {
startTime = Date.now();
}
current = Date.now() - startTime;
const totalInSeconds = totalInMilliseconds / 1000;
percentage = Math.min((current / 1000) / totalInSeconds, 1);
const progress = Math.round((size * percentage));
const emptyProgress = size - progress;
const progressText = '▇'.repeat(progress);
const emptyProgressText = '—'.repeat(emptyProgress);
const percentageText = Math.round(percentage * 100) + '%';
let elapsedTimeText = new Date(current).toISOString().slice(11, -5);
let totalTimeText = new Date(totalInMilliseconds).toISOString().slice(11, -5);
if (totalTimeText.startsWith('00:')) {
elapsedTimeText = elapsedTimeText.slice(3);
totalTimeText = totalTimeText.slice(3);
}
const progressBarString = elapsedTimeText + ' [' + progressText + emptyProgressText + ']' + percentageText + ' ' + totalTimeText; // Creating and returning the bar
return { progressBarString, isDone: percentage === 1 };
}
module.exports.progressBar = progressBar;

View File

@@ -1,47 +1,56 @@
const { SlashCommandBuilder } = require('@discordjs/builders'); const { SlashCommandBuilder } = require("@discordjs/builders");
const { REST } = require('@discordjs/rest'); const { REST } = require("@discordjs/rest");
const { Routes } = require('discord-api-types/v9'); const { Routes } = require("discord-api-types/v9");
async function registerCommands(clientId, token) { async function registerCommands(clientId, token) {
const commands = [ const commands = [
new SlashCommandBuilder() new SlashCommandBuilder()
.setName('play') .setName("play")
.setDescription('Plays songs!') .setDescription("Plays songs!")
.addStringOption(option => .addStringOption((option) =>
option.setName('input') option
.setDescription('Play song from YouTube, Spotify, SoundCloud, etc.') .setName("input")
.setDescription("Play song from YouTube, Spotify, SoundCloud, etc.")
.setRequired(true) .setRequired(true)
), ),
new SlashCommandBuilder() new SlashCommandBuilder()
.setName('queue') .setName("queue")
.setDescription('Adds a song to the queue!') .setDescription("Adds a song to the queue!")
.addStringOption(option => .addStringOption((option) =>
option.setName('song') option
.setDescription('Add song from YouTube, Spotify, SoundCloud, etc. to the queue') .setName("song")
.setDescription(
"Add song from YouTube, Spotify, SoundCloud, etc. to the queue"
)
.setRequired(true) .setRequired(true)
), ),
new SlashCommandBuilder() new SlashCommandBuilder()
.setName('pause') .setName("pause")
.setDescription('Pauses the current song!'), .setDescription("Pauses the current song!"),
new SlashCommandBuilder() new SlashCommandBuilder()
.setName('resume') .setName("resume")
.setDescription('Resumes the current song!'), .setDescription("Resumes the current song!"),
new SlashCommandBuilder() new SlashCommandBuilder()
.setName('loop') .setName("loop")
.setDescription('Loops the current song! (toggle)'), .setDescription("Loops the current song! (toggle)"),
new SlashCommandBuilder()
.setName("stop")
.setDescription("Stops the current song!"),
]; ];
const rest = new REST({ version: '9' }).setToken(token); const rest = new REST({
version: "9",
})
.setToken(token);
try { try {
console.log('Started refreshing application (/) commands.'); console.log("\x1b[35m", "Started refreshing application (/) commands.");
await rest.put( await rest.put(Routes.applicationCommands(clientId), {
Routes.applicationCommands(clientId), body: commands,
{ body: commands }, });
);
console.log('Successfully reloaded application (/) commands.'); console.log("\x1b[35m", "Successfully reloaded application (/) commands.");
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }