Merge pull request #2 from Myxelium/pause-resume-loop

Halfbroken fixes
This commit was merged in pull request #2.
This commit is contained in:
2023-06-28 03:04:32 +02:00
committed by GitHub
18 changed files with 545 additions and 421 deletions

36
.eslintrc.js Normal file
View File

@@ -0,0 +1,36 @@
module.exports = {
env: {
browser: true,
commonjs: true,
es2021: true,
},
extends: 'airbnb-base',
overrides: [{
env: {
node: true,
},
files: [
'.eslintrc.{js,cjs}',
],
parserOptions: {
sourceType: 'script',
},
},
],
parserOptions: {
ecmaVersion: 'latest',
},
rules: {
// "indent": ["error", { "allowIndentationTabs": true }],
// "allowIndentationTabs": true,
// 'no-tabs': 0,
indent: ['error', 'tab'],
'no-tabs': ['error', { allowIndentationTabs: true }],
'no-multi-spaces': 'error',
'object-curly-newline': 'off',
'no-trailing-spaces': 'error',
'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }],
'no-duplicate-imports': ['error', { includeExports: true }],
'eol-last': ['error', 'never'],
},
};

10
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,10 @@
{
"prettier.useTabs": true,
"editor.codeActionsOnSave": {
"source.fixAll": true
},
"eslint.validate": ["javascript"]
// "editor.formatOnSave": true,
// "eslint.format.enable": true
}

View File

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

View File

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

View File

@@ -1,23 +1,22 @@
const { getMusicStream } = require("./../utils/getMusicStream");
const musicQueue = require("../musicQueue");
const { musicPlayer } = require("../utils/musicPlayer");
const { joinVoiceChannel } = require("@discordjs/voice");
/* eslint-disable consistent-return */
const { joinVoiceChannel } = require('@discordjs/voice');
const { getMusicStream } = require('../utils/getMusicStream');
const musicQueue = require('../musicQueue');
const { musicPlayer } = require('../utils/musicPlayer');
async function playCommand(interaction) {
await interaction.deferReply();
const query = interaction.options.getString("input");
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."
'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,
@@ -26,7 +25,17 @@ async function playCommand(interaction) {
selfMute: false,
});
musicPlayer(interaction.guild.id, connection, interaction);
if (musicQueue.getQueue(interaction.guild.id).length > 0) {
musicQueue.removeFromQueue(interaction.guild.id);
}
musicQueue.addToQueue(interaction.guild.id, song);
musicPlayer(
interaction.guild.id,
connection,
interaction,
);
}
module.exports.playCommand = playCommand;

View File

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

View File

@@ -1,5 +1,5 @@
const musicQueue = require("../musicQueue");
const { getVoiceConnection } = require("@discordjs/voice");
const { getVoiceConnection } = require('@discordjs/voice');
const musicQueue = require('../musicQueue');
async function stopCommand(interaction) {
await interaction.deferReply();
@@ -9,7 +9,7 @@ async function stopCommand(interaction) {
if (!voiceChannel) {
return interaction.followUp(
"You must be in a voice channel to use this command."
'You must be in a voice channel to use this command.',
);
}
@@ -17,14 +17,14 @@ async function stopCommand(interaction) {
if (!connection.state.subscription.player) {
return interaction.followUp(
"I am not currently playing music in a voice channel."
'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.");
return interaction.followUp('Stopped the music and cleared the queue.');
}
module.exports.stopCommand = stopCommand;

View File

@@ -1,4 +1,5 @@
const { Client, GatewayIntentBits } = require('discord.js');
const process = require('dotenv').config();
const { registerCommands } = require('./utils/registerCommands');
const { playCommand } = require('./commands/play');
const { queueCommand } = require('./commands/queue');
@@ -7,9 +8,8 @@ const { pauseCommand, unpauseCommand } = require('./commands/pauseResume');
const { toggleLoopCommand } = require('./commands/loop');
const { ReAuth } = require('./reAuthenticate');
const process = require('dotenv').config();
const clientId = process.parsed.clientId;
const token = process.parsed.token;
const { clientId } = process.parsed;
const { token } = process.parsed;
const client = new Client({
intents: [
@@ -17,9 +17,9 @@ const client = new Client({
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildVoiceStates,
GatewayIntentBits.GuildIntegrations
]
})
GatewayIntentBits.GuildIntegrations,
],
});
client.on('ready', async () => {
console.log(`Logged in as ${client.user.tag}!`);
@@ -33,7 +33,7 @@ client.on('interactionCreate', async (interaction) => {
const { commandName } = interaction;
if (commandName === 'play') {
await playCommand(interaction);
await playCommand(interaction, client);
} else if (commandName === 'queue') {
await queueCommand(interaction);
} else if (commandName === 'pause') {
@@ -48,7 +48,7 @@ client.on('interactionCreate', async (interaction) => {
});
client.on('messageCreate', async (message) => {
if(message.content == 'reauth') {
if (message.content === 'reauth') {
await ReAuth();
}
});

View File

@@ -1,8 +1,8 @@
const playdl = require("play-dl");
const playdl = require('play-dl');
async function getStream(query) {
let songInformation = await playdl.soundcloud(query);
let stream = await playdl.stream_from_info(songInformation, { quality: 2 });
const songInformation = await playdl.soundcloud(query);
const stream = await playdl.stream_from_info(songInformation, { quality: 2 });
return {
title: songInformation.name,
stream: stream.stream,

View File

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

View File

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

View File

@@ -5,7 +5,8 @@
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js"
"start": "node index.js",
"lint": "eslint --fix --ext .js,.jsx ."
},
"repository": {
"type": "git",
@@ -22,7 +23,9 @@
"dependencies": {
"@discordjs/builders": "^1.6.3",
"@discordjs/opus": "^0.9.0",
"@discordjs/rest": "^1.7.1",
"@discordjs/voice": "^0.16.0",
"discord-api-types": "^0.37.46",
"discord.js": "^14.11.0",
"dotenv": "^16.3.1",
"ffmpeg-static": "^4.2.7",
@@ -33,6 +36,11 @@
"tweetnacl": "^1.0.3",
"ytdl-core": "^4.11.4",
"ytsr": "^3.8.2"
},
"devDependencies": {
"eslint": "^8.43.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.27.5",
"prettier": "^2.8.8"
}
}

View File

@@ -1,18 +1,17 @@
const play = require('play-dl');
async function ReAuth() {
play.getFreeClientID().then((clientID) => {
play.getFreeClientID().then((clientID) => {
play.setToken({
soundcloud : {
client_id : clientID
}
})
soundcloud: {
client_id: clientID,
},
});
console.log('Soudncloud Client ID: ' + clientID);
})
console.log(`Soudncloud Client ID: ${clientID}`);
});
play.authorization();
play.authorization();
}
module.exports.ReAuth = ReAuth;

View File

@@ -1,31 +1,28 @@
const { StreamType } = require('@discordjs/voice');
const spotify = require('../music_sources/spotify');
const soundcloud = require('../music_sources/soundcloud');
const youtube = require('../music_sources/youtube');
const { StreamType } = require('@discordjs/voice');
async function getMusicStream(query) {
let stream;
let songTitle;
let songDuration;
let type = StreamType.Opus;
let userInput = query;
const userInput = query;
if (query.includes('spotify.com')) {
stream = await spotify.getStream(query);
songTitle = stream.title;
songDuration = stream.duration;
stream = stream.stream;
} else if (query.includes('soundcloud.com')) {
stream = await soundcloud.getStream(query);
songTitle = stream.title;
songDuration = stream.duration;
stream = stream.stream;
type = StreamType.OggOpus;
} else {
stream = await youtube.getStream(query)
stream = await youtube.getStream(query);
songTitle = stream?.title ?? 'Unknown';
songDuration = stream?.duration ?? 'Unknown';
stream = stream?.stream;
@@ -35,9 +32,9 @@ async function getMusicStream(query) {
return {
title: songTitle,
duration: songDuration,
userInput: userInput,
stream: stream,
type: type
userInput,
stream,
type,
};
}

View File

@@ -1,74 +1,143 @@
/* eslint-disable max-len */
/* eslint-disable no-restricted-syntax */
const {
createAudioResource,
createAudioPlayer,
NoSubscriberBehavior,
AudioPlayerStatus,
} = require('@discordjs/voice');
const process = require('dotenv').config();
const { clientId } = process.parsed;
const { EmbedBuilder } = require('discord.js');
const musicQueue = require('../musicQueue');
const { progressBar } = require('../utils/progress');
const { progressBar } = require('./progress');
const currentInteractionIds = new Map();
const currentInteractions = new Map();
const oldConnections = [];
const timeoutTimer = new Map();
// TODO FIX THIS SHIT!!! ISSUES WITH DISPLAYING NAME AND STATUS WHEN UPDATING
function nowPLayingMessage(interaction, song, oldInteractionId) {
progressBar(0, 0, true);
if (interaction.commandName === 'play') {
interaction.followUp('~🎵~').then((message) => {
const songTitle = song.title;
// const embed = new EmbedBuilder()
// .setColor('#E0B0FF')
// .setTitle(`Now playing: ${songTitle}`)
// .setDescription(
// progressBar(song.duration, 10).progressBarString,
// );
const embed = new EmbedBuilder()
.setColor('#E0B0FF')
.setTitle(`Now playing: ${songTitle}`);
message.edit({
embeds: [embed],
});
const inter = setInterval(async () => {
const { progressBarString, isDone } = progressBar(
song.duration,
10,
);
if (isDone || message.id !== oldInteractionId) {
// clearInterval(inter);
return;
}
if (message.id != null && interaction.guild.id !== oldInteractionId) {
interaction.channel.messages.fetch().then(async (channel) => {
const filter = channel.filter((msg) => msg.author.id === clientId);
const latestMessage = await interaction.channel.messages.fetch(filter.first().id);
latestMessage.edit({
embeds: [embed.setTitle(`Now playing: ${songTitle}`)],
});
});
}
}, 2000);
currentInteractionIds.set(interaction.guild.id, interaction);
currentInteractions.set(interaction.guild.id, interaction.id);
});
}
}
async function musicPlayer(guildId, connection, interaction) {
try {
const oldInteractions = await currentInteractions.get(interaction.guild.id);
const oldInteractionId = await currentInteractionIds.get(interaction.guild.id);
const serverQueue = musicQueue.getQueue(guildId);
const oldConnection = oldConnections
.find((guidConnection) => guidConnection[0] === interaction.guild.id);
if (serverQueue.length === 0) {
connection.destroy();
return;
oldConnection[1].destroy();
}
const song = serverQueue[0];
if (song.stream == null) {
if (song.stream === undefined) {
musicQueue.removeFromQueue(guildId);
musicPlayer(guildId, connection);
return;
}
let resource = createAudioResource(song.stream, {
inputType: song.type
})
let player = createAudioPlayer({
behaviors: {
noSubscriber: NoSubscriberBehavior.Play
}
})
player.play(resource)
connection.subscribe(player)
progressBar(0, 0, true)
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)
const resource = createAudioResource(song.stream, {
inputType: song.type,
});
}
const player = createAudioPlayer({
behaviors: {
noSubscriber: NoSubscriberBehavior.Play,
},
});
player.play(resource);
connection.subscribe(player);
nowPLayingMessage(interaction, song, oldInteractions);
oldConnections.push([interaction.guild.id, connection]);
player.on(AudioPlayerStatus.Idle, async () => {
console.log('Song ended:', song.title);
await musicQueue.removeFromQueue(guildId)
if (serverQueue.length !== 1) {
await musicQueue.removeFromQueue(guildId);
musicPlayer(guildId, connection, interaction);
}
// timeoutTimer.set(guildId, setTimeout(async () => {
// await musicQueue.removeFromQueue(guildId);
// connection.destroy();
// }, 10000));
});
return player;
player.on(AudioPlayerStatus.Playing, () => {
console.log('pausing timer');
clearTimeout(
timeoutTimer.get(guildId),
);
});
if (oldInteractionId) {
oldInteractionId.channel.messages.fetch().then(async (channel) => {
const { lastMessage } = oldInteractionId.channel;
const filter = channel.filter((message) => message.author.id === clientId && message.id !== lastMessage.id);
setTimeout(() => {
oldInteractionId.channel.bulkDelete(filter);
}, 1000);
});
}
} catch (error) {
console.error(error);
interaction.followUp('There was an error playing the song.');
}
}
module.exports.musicPlayer = musicPlayer;

View File

@@ -21,7 +21,6 @@ const progressBar = (totalInMilliseconds, size, reset = false) => {
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);
@@ -31,8 +30,8 @@ const progressBar = (totalInMilliseconds, size, reset = false) => {
totalTimeText = totalTimeText.slice(3);
}
const progressBarString = elapsedTimeText + ' [' + progressText + emptyProgressText + ']' + percentageText + ' ' + totalTimeText; // Creating and returning the bar
const progressBarString = `${elapsedTimeText} \`\`\`${progressText}${emptyProgressText}\`\`\` ${totalTimeText}`; // Creating and returning the bar
return { progressBarString, isDone: percentage === 1 };
}
};
module.exports.progressBar = progressBar;

View File

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