diff --git a/.env.example b/.env.example index 87a8f0b..d9b5e4b 100644 --- a/.env.example +++ b/.env.example @@ -7,10 +7,6 @@ OWNER_ID=360815761662541824 EMBED_COLOUR='#FF0000' PRESENCE='/help | elite-bot.com' -# DJ MODE SETTINGS -ENABLE_DJMODE=false -DJ_ROLE=514885071438872604 - # MUSIC PLAYBACK BEHAVIOUR SETTINGS LEAVE_ON_EMPTY=true LEAVE_ON_EMPTY_COOLDOWN=300000 @@ -23,4 +19,15 @@ LEAVE_ON_STOP_COOLDOWN=300000 SELF_DEAFEN=true DEFAULT_VOLUME=50 -SMOOTH_VOLUME=true \ No newline at end of file +SMOOTH_VOLUME=true + +### ADDITIONAL FEATURES SETTINGS + +# DJ MODE SETTINGS +ENABLE_DJMODE=false +DJ_ROLE=123456789987654321 + +# PLEX MEDIA SERVER SETTINGS +ENABLE_PLEX=false +PLEX_SERVER='http://YOUR_IPADDRESS:32400' +PLEX_AUTHTOKEN='YOUR_AUTH_TOKEN' \ No newline at end of file diff --git a/README.md b/README.md index 252ea1b..eb2f835 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,10 @@ Elite Music is a feature-packed Discord Music Bot built on top of [discord.js](h - Support for various streaming platforms - Wide range of audio filters - Awesome playback UI +- Additional features including plex media server support. - And much more! -Elite Music was originally a part of my verified Discord bot... [Elite Bot](https://elite-bot.com/), but the music section had to be removed due to verification issues. Therefore, in a win situation, I decided to open-source the bot's music code in this repository. And even better, I decided to re-write the entire bot to improve the feature set and overall quality of the code. +Elite Music was originally a part of my verified Discord bot... [Elite Bot](https://elite-bot.com/), but the music section had to be removed due to verification issues. Therefore, in a win situation, I decided to open-source the bot's music code in this repository. And even better, I decided to rewrite the entire bot to improve the code's feature set and overall quality. # Installation 🔌 ## Prerequisites 🛂 @@ -18,12 +19,12 @@ In order for the bot to function correctly, there are a few prerequisites that y - [NodeJS](https://nodejs.org/en) - For the bot to function, it must be running in a NodeJS environment running on v16.9.0 or higher. It is highly recommended that you download the LTS build which is available for your OS to remain on the latest stable version. Head over to the [NodeJS Download website](https://nodejs.org/en/download) to download and install an appropriate version. -- [FFmpeg](https://ffmpeg.org/) or Avconv - You will require either of these for transcoding. It is recommended to use FFmpeg. By default, the bot comes with the `ffmpeg-static` binaries as a dependancy which allows the bot to work out of the box. Alternatively, you may decide to set your own ffmpeg binaries. In this case, head over to the [FFmpeg Download website](https://ffmpeg.org/download.html), select your OS and download the appropriate package. You can then place `FFMPEG_PATH` as a new option into your .ENV file stating the custom path to your custom FFmpeg binaries. +- [FFmpeg](https://ffmpeg.org/) or Avconv - You will require either of these for transcoding. It is recommended to use FFmpeg. By default, the bot comes with the `ffmpeg-static` binaries as a dependency which allows the bot to work out of the box. Alternatively, you may decide to set your own ffmpeg binaries. In this case, head over to the [FFmpeg Download website](https://ffmpeg.org/download.html), select your OS and download the appropriate package. You can then place `FFMPEG_PATH` as a new option into your `.ENV` file stating the custom path to your custom FFmpeg binaries. -- [Discord Bot Account](https://discord.com/developers/applications) - You must register a bot on the Discord Developer site to gain access to a token to run the bot. Head over to the [Developer website](https://discord.com/developers/applications) and click on `New Application` button. Provide a name and press `Create`. Next on the left-hand menu, select the `Bot` section and press `Add Bot` alongside the confirmation. Finally, press `Reset Token` and finally copy the token and keep it safe. This is what you will have to place into your .ENV file for the bot to function. +- [Discord Bot Account](https://discord.com/developers/applications) - You must register a bot on the Discord Developer site to access a token to run the bot. Head over to the [Developer website](https://discord.com/developers/applications) and click on `New Application` button. Provide a name and press `Create`. Next on the left-hand menu, select the `Bot` section and press `Add Bot` alongside the confirmation. Finally, press `Reset Token` and finally copy the token and keep it safe. This is what you will have to place into your `.ENV` file for the bot to function. ## Setup 🔧 -The first step is to clone the repository or download it manually as a folder to host it directly. The Git option is recommended for more advanced users and for users which already have it installed. +The first step is to clone the repository or download it manually as a folder to host it directly. The Git option is recommended for more advanced users and for users who already have it installed. #### Basic download Head over to the download page and download the .zip source code. Next, using a tool such as [7-Zip](https://www.7-zip.org/), extract the files from the .zip folder. You can now move on to the following steps. @@ -34,7 +35,7 @@ An alternative way to download the repository is through the usage of [Git](http #### Continuing the Setup Now that you have downloaded the repository, you can continue with the following steps. -1. Open a new command/shell/terminal window within your new folder. You should be able to right-click and open windows terminal/command prompt if on windows. +1. Open a new command/shell/terminal window within your new folder. You should be able to right-click and open the Windows terminal/command prompt if on Windows. 2. Run the command `npm install` to download all of the module dependencies. 3. Rename the file `.env.example` to simply `.env`. Once down, edit the `.env` file with the configuration options that you would like! 4. Finally, run your bot using `node .` within a command/shell/terminal window. The bot should now become online and provide a success message if everything was configured correctly. 🎉 @@ -42,10 +43,30 @@ Now that you have downloaded the repository, you can continue with the following Of course, you need to add your bot to your server now in order to use it. Follow this [useful guide](https://discordjs.guide/preparations/adding-your-bot-to-servers.html#bot-invite-links) from the discord.js Guide which explains how to do this with great detail if you need help understanding how to do this. +## Optional Features ✅ +You may decide to want to enable additional optional features for your bot. Follow the appropriate sub-heading to learn how to set up and enable the selected feature! + +If you are missing the relevant option in your environmental (`.env`) file, make sure to check the latest [`.env.sample` file](https://github.com/ThatGuyJacobee/Elite-Music/blob/main/.env.example) to ensure you are on the latest version. + +Once you have followed the appropriate steps for the optional feature that you want to enable, you should start the bot and ensure that the configuration option returns as `true` when the configuration loads. If the feature still shows as disabled, this suggests that you have a configuration error. Follow the error logs that are provided in your console to resolve this. If you are still having trouble with your issue, feel free to create an issue on the [repository](https://github.com/ThatGuyJacobee/Elite-Music/issues/new) or join the [Support Discord server](https://discord.elitegami.ng). + +### Plex Media Server playback +The Plex optional feature when enabled, allows you to stream music directly from your Plex Media Server through the /plex command. In order to enable the Plex feature, you must go into your `.env` file and set up the configuration to your own Plex Media Server. + +1. Firstly, set `ENABLE_PLEX` to `true`. +2. Next, you must provide a direct URL to your Plex Media Center. The default port that Plex Media Server runs on is `32400`. You can test that your `PLEX_SERVER` URL is correct, by pasting it into any web browser, and it should load successfully with a login page. +3. Finally, you must place your plex authentication token into the `PLEX_AUTHTOKEN` field. You can do this by browsing the XML file for a library item. Please follow the [official Plex Support article](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/) to access your token. Once you have access to it, place it into your .env file. + +### DJ Mode +Elite Music comes with a DJ Mode optional feature, which locks down the use of commands and interactions to members who have the specified DJ Role. + +1. Firstly, set `ENABLE_DJMODE` to `true`. +2. Now create a role on your server which you wish to be used as the DJ Role. Copy the ID of the role and place it into the `DJ_ROLE` field. + ## Support 🆘 Need help setting up the bot or experiencing some trouble? Feel free to head over to the [Support Discord server](https://discord.elitegami.ng) and let me know! -Found a bug or issue with the latest build? Feel free to open an issue on this repository! I will respond as soon as possible. +Found a bug or issue with the latest build? Feel free to open an issue on this [repository](https://github.com/ThatGuyJacobee/Elite-Music/issues/new)! I will respond as soon as possible. ## Elite Bot - Verified Multi-Purpose Bot 💪 Looking for a multi-purpose Discord Bot for your server? Look no further, check out Elite Bot to fulfil all of your server needs including moderation, logging, external server status and much more! diff --git a/commands/music/audiofilter.js b/commands/music/audiofilter.js index f681430..648b4c1 100644 --- a/commands/music/audiofilter.js +++ b/commands/music/audiofilter.js @@ -1,7 +1,7 @@ require("dotenv").config(); const { SlashCommandBuilder } = require("@discordjs/builders"); -const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); -const { Player, QueryType } = require('discord-player'); +const { EmbedBuilder } = require("discord.js"); +const { Player } = require('discord-player'); module.exports = { data: new SlashCommandBuilder() @@ -52,12 +52,12 @@ module.exports = { ), async execute(interaction) { const filter = interaction.options.getString("filter"); - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); diff --git a/commands/music/back.js b/commands/music/back.js index 26f0b43..8669b66 100644 --- a/commands/music/back.js +++ b/commands/music/back.js @@ -1,19 +1,19 @@ require("dotenv").config(); const { SlashCommandBuilder } = require("@discordjs/builders"); -const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); -const { Player, QueryType } = require('discord-player'); +const { EmbedBuilder } = require("discord.js"); +const { Player } = require('discord-player'); module.exports = { data: new SlashCommandBuilder() .setName("back") .setDescription("Play the previous song!"), async execute(interaction) { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); @@ -27,7 +27,7 @@ module.exports = { .setThumbnail(interaction.guild.iconURL({dynamic: true})) .setColor(process.env.EMBED_COLOUR) .setTitle(`Playing previous song ⏮️`) - .setDescription(`Returning next to the previous song ${previousTracks[0].title} ([Link](${previousTracks[0].url}))!`) + .setDescription(`Returning next to the previous song ${previousTracks[0].title} ${previousTracks[0].queryType != 'arbitrary' ? `([Link](${previousTracks[0].url}))` : ''}!`) .setTimestamp() .setFooter({ text: `Requested by: ${interaction.user.tag}` }) diff --git a/commands/music/clearqueue.js b/commands/music/clearqueue.js index a37f7a8..6ab91e6 100644 --- a/commands/music/clearqueue.js +++ b/commands/music/clearqueue.js @@ -1,19 +1,19 @@ require("dotenv").config(); const { SlashCommandBuilder } = require("@discordjs/builders"); -const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); -const { Player, QueryType } = require('discord-player'); +const { EmbedBuilder } = require("discord.js"); +const { Player } = require('discord-player'); module.exports = { data: new SlashCommandBuilder() .setName("clearqueue") .setDescription("Clear the current music queue!"), async execute(interaction) { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); diff --git a/commands/music/jumpqueue.js b/commands/music/jumpqueue.js index 847eb5b..55a21d0 100644 --- a/commands/music/jumpqueue.js +++ b/commands/music/jumpqueue.js @@ -1,7 +1,7 @@ require("dotenv").config(); const { SlashCommandBuilder } = require("@discordjs/builders"); -const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); -const { Player, QueryType } = require('discord-player'); +const { EmbedBuilder } = require("discord.js"); +const { Player } = require('discord-player'); module.exports = { data: new SlashCommandBuilder() @@ -13,12 +13,12 @@ module.exports = { .setRequired(true) ), async execute(interaction) { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); @@ -33,7 +33,7 @@ module.exports = { .setThumbnail(interaction.guild.iconURL({dynamic: true})) .setColor(process.env.EMBED_COLOUR) .setTitle(`Jumped to song ⏭️`) - .setDescription(`Now playing: ${queuedTracks[trackIndex].title} ([Link](${queuedTracks[trackIndex].url}))!`) + .setDescription(`Now playing: ${queuedTracks[trackIndex].title} ${queuedTracks[trackIndex].queryType != 'arbitrary' ? `([Link](${queuedTracks[trackIndex].url}))` : ''}!`) .setTimestamp() .setFooter({ text: `Requested by: ${interaction.user.tag}` }) diff --git a/commands/music/loop.js b/commands/music/loop.js index 5edfdbe..a84f460 100644 --- a/commands/music/loop.js +++ b/commands/music/loop.js @@ -1,7 +1,7 @@ require("dotenv").config(); const { SlashCommandBuilder } = require("@discordjs/builders"); -const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); -const { Player, QueueRepeatMode, QueryType } = require('discord-player'); +const { EmbedBuilder } = require("discord.js"); +const { Player, QueueRepeatMode } = require('discord-player'); module.exports = { data: new SlashCommandBuilder() @@ -32,12 +32,12 @@ module.exports = { ), async execute(interaction) { const loopmode = interaction.options.getInteger("loopmode"); - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); diff --git a/commands/music/nowplaying.js b/commands/music/nowplaying.js index 9ea8c4a..152a3b6 100644 --- a/commands/music/nowplaying.js +++ b/commands/music/nowplaying.js @@ -1,14 +1,14 @@ const { SlashCommandBuilder } = require("@discordjs/builders"); -const { EmbedBuilder, PermissionFlagsBits, ButtonBuilder, ActionRowBuilder } = require("discord.js"); -const { Player, QueryType } = require('discord-player'); +const { EmbedBuilder, ButtonBuilder, ActionRowBuilder, AttachmentBuilder } = require("discord.js"); +const { Player } = require('discord-player'); module.exports = { data: new SlashCommandBuilder() .setName("nowplaying") .setDescription("Check the currently playing song!"), async execute(interaction) { - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); @@ -17,12 +17,13 @@ module.exports = { const progress = queue.node.createProgressBar(); var create = progress.replace(/ 0:00/g, ' ◉ LIVE'); + var coverImage = new AttachmentBuilder(queue.currentTrack.thumbnail, { name: 'coverimage.jpg', description: `Song Cover Image for ${queue.currentTrack.title}` }) const npembed = new EmbedBuilder() .setAuthor({ name: interaction.client.user.tag, iconURL: interaction.client.user.displayAvatarURL() }) - .setThumbnail(queue.currentTrack.thumbnail) + .setThumbnail('attachment://coverimage.jpg') .setColor(process.env.EMBED_COLOUR) .setTitle(`Now playing 🎵`) - .setDescription(`${queue.currentTrack.title} ([Link](${queue.currentTrack.url}))\n${create}`) + .setDescription(`${queue.currentTrack.title} ${queue.currentTrack.queryType != 'arbitrary' ? `([Link](${queue.currentTrack.url}))` : ''}\n${create}`) //.addField('\u200b', progress.replace(/ 0:00/g, ' ◉ LIVE')) .setTimestamp() @@ -78,6 +79,6 @@ module.exports = { ) ]; - interaction.reply({ embeds: [npembed], components: finalComponents }) + interaction.reply({ embeds: [npembed], components: finalComponents, files: [coverImage] }) } } \ No newline at end of file diff --git a/commands/music/pause.js b/commands/music/pause.js index c1ee1db..9a243e1 100644 --- a/commands/music/pause.js +++ b/commands/music/pause.js @@ -1,19 +1,19 @@ require("dotenv").config(); const { SlashCommandBuilder } = require("@discordjs/builders"); -const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); -const { Player, QueryType } = require('discord-player'); +const { EmbedBuilder } = require("discord.js"); +const { Player } = require('discord-player'); module.exports = { data: new SlashCommandBuilder() .setName("pause") .setDescription("Pause the current song at the current time!"), async execute(interaction) { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); @@ -25,7 +25,7 @@ module.exports = { .setThumbnail(queue.currentTrack.thumbnail) .setColor(process.env.EMBED_COLOUR) .setTitle(`Song paused ⏸️`) - .setDescription(`Playback has been **${checkPause ? 'resumed' : 'paused'}**. Currently playing ${queue.currentTrack.title} ([Link](${queue.currentTrack.url}))!`) + .setDescription(`Playback has been **${checkPause ? 'resumed' : 'paused'}**. Currently playing ${queue.currentTrack.title} ${queue.currentTrack.queryType != 'arbitrary' ? `([Link](${queue.currentTrack.url}))` : ''}!`) .setTimestamp() .setFooter({ text: `Requested by: ${interaction.user.tag}` }) diff --git a/commands/music/play.js b/commands/music/play.js index 1bbe32b..7f9c026 100644 --- a/commands/music/play.js +++ b/commands/music/play.js @@ -1,6 +1,6 @@ require("dotenv").config(); const { SlashCommandBuilder } = require("@discordjs/builders"); -const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); +const { EmbedBuilder } = require("discord.js"); const { Player, QueryType } = require('discord-player'); module.exports = { @@ -13,13 +13,12 @@ module.exports = { .setRequired(true) ), async execute(interaction) { - if (process.env.ENABLE_DJMODE == true) { + if (client.config.enableDjMode) { if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); } - await interaction.deferReply(); - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); const query = interaction.options.getString("music"); @@ -52,9 +51,12 @@ module.exports = { }) if (!search || search.tracks.length == 0 || !search.tracks) { - return interaction.followUp({ content: `❌ | Ooops... something went wrong, couldn't find the song with the requested query.`, ephemeral: true }) + return interaction.reply({ content: `❌ | Ooops... something went wrong, couldn't find the song with the requested query.`, ephemeral: true }) } + //Otherwise it has found so defer reply + await interaction.deferReply(); + try { if (!queue.connection) await queue.connect(interaction.member.voice.channel); } @@ -101,7 +103,7 @@ module.exports = { .setThumbnail(search.tracks[0].thumbnail) .setColor(process.env.EMBED_COLOUR) .setTitle(`Started playback ▶️`) - .setDescription(`Began playing the song **${search.tracks[0].title}** ([Link](${search.tracks[0].url}))!`) + .setDescription(`Began playing the song **${search.tracks[0].title}** ${search.tracks[0].queryType != 'arbitrary' ? `([Link](${search.tracks[0].url}))` : ''}!`) .setTimestamp() .setFooter({ text: `Requested by: ${interaction.user.tag}` }) @@ -129,7 +131,7 @@ module.exports = { .setThumbnail(search.tracks[0].thumbnail) .setColor(process.env.EMBED_COLOUR) .setTitle(`Added to queue ⏱️`) - .setDescription(`Added song **${search.tracks[0].title}** ([Link](${search.tracks[0].url})) to the queue!`) + .setDescription(`Began playing the song **${search.tracks[0].title}** ${search.tracks[0].queryType != 'arbitrary' ? `([Link](${search.tracks[0].url}))` : ''}!`) .setTimestamp() .setFooter({ text: `Requested by: ${interaction.user.tag}` }) diff --git a/commands/music/playnext.js b/commands/music/playnext.js index 78efa9f..316a09f 100644 --- a/commands/music/playnext.js +++ b/commands/music/playnext.js @@ -1,6 +1,6 @@ require("dotenv").config(); const { SlashCommandBuilder } = require("@discordjs/builders"); -const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); +const { EmbedBuilder } = require("discord.js"); const { Player, QueryType } = require('discord-player'); module.exports = { @@ -13,13 +13,12 @@ module.exports = { .setRequired(true) ), async execute(interaction) { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - await interaction.deferReply(); - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); const query = interaction.options.getString("music"); @@ -52,13 +51,16 @@ module.exports = { }) if (!search || search.tracks.length == 0 || !search.tracks) { - return interaction.followUp({ content: `❌ | Ooops... something went wrong, couldn't find the song with the requested query.`, ephemeral: true }) + return interaction.reply({ content: `❌ | Ooops... something went wrong, couldn't find the song with the requested query.`, ephemeral: true }) } if (search.playlist) { - return interaction.followUp({ content: `❌ | Ooops... you can only add single songs with this command. Use the regular **/play** command to add playlists to the queue.`, ephemeral: true }) + return interaction.reply({ content: `❌ | Ooops... you can only add single songs with this command. Use the regular **/play** command to add playlists to the queue.`, ephemeral: true }) } + //Otherwise it has found so defer reply + await interaction.deferReply(); + try { if (!queue.connection) await queue.connect(interaction.member.voice.channel); } @@ -92,7 +94,7 @@ module.exports = { .setThumbnail(search.tracks[0].thumbnail) .setColor(process.env.EMBED_COLOUR) .setTitle(`Added to the top of the queue ⏱️`) - .setDescription(`Added song **${search.tracks[0].title}** ([Link](${search.tracks[0].url})) to the top of the queue (playing next)!`) + .setDescription(`Added song **${search.tracks[0].title}** ${search.tracks[0].queryType != 'arbitrary' ? `([Link](${search.tracks[0].url}))` : ''} to the top of the queue (playing next)!`) .setTimestamp() .setFooter({ text: `Requested by: ${interaction.user.tag}` }) diff --git a/commands/music/plex.js b/commands/music/plex.js new file mode 100644 index 0000000..6b99849 --- /dev/null +++ b/commands/music/plex.js @@ -0,0 +1,547 @@ +require("dotenv").config(); +const { SlashCommandBuilder } = require("@discordjs/builders"); +const { ActionRowBuilder, EmbedBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, AttachmentBuilder } = require("discord.js"); +const { Player, QueryType, Track } = require('discord-player'); + +module.exports = { + data: new SlashCommandBuilder() + .setName("plex") + .setDescription("Play a song into the queue!") + .addSubcommand((subcommand) => subcommand + .setName("play") + .setDescription("Play a song from your plex.") + .addStringOption((option) => option + .setName("music") + .setDescription("Name of the song you want to play.") + .setRequired(true) + ) + ) + .addSubcommand((subcommand) => subcommand + .setName("search") + .setDescription("Search songs and playlists.") + .addStringOption((option) => option + .setName("music") + .setDescription("Search query for a single song or playlist.") + .setRequired(true) + ) + ), + async execute(interaction) { + if (interaction.options.getSubcommand() === "play") { + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); + } + + if (!client.config.enablePlex) { + return interaction.reply({ content: `❌ | Plex is currently disabled! Ask the server admin to enable and configure this in the environment file.`, ephemeral: true }); + } + + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + + const player = Player.singleton(); + const query = interaction.options.getString("music"); + var checkqueue = player.nodes.get(interaction.guild.id); + + if (!checkqueue) { + player.nodes.create(interaction.guild.id, { + leaveOnEmpty: client.config.leaveOnEmpty, + leaveOnEmptyCooldown: client.config.leaveOnEmptyCooldown, + leaveOnEnd: client.config.leaveOnEnd, + leaveOnEndCooldown: client.config.leaveOnEndCooldown, + leaveOnStop: client.config.leaveOnStop, + leaveOnStopCooldown: client.config.leaveOnStopCooldown, + selfDeaf: client.config.selfDeafen, + skipOnNoStream: true, + metadata: { + channel: interaction.channel, + requestedBy: interaction.user, + client: interaction.guild.members.me, + } + }) + } + + var queue = player.nodes.get(interaction.guild.id); + + const fetch = require('node-fetch'); + try { + const search = await fetch(`${client.config.plexServer}/search/?X-Plex-Token=${client.config.plexAuthtoken}&query=${query}&limit=10&type=10,15`, { + method: 'GET', + headers: { accept: 'application/json'} + }) + + var searchRes = await search.json() + //console.log(searchRes) + + if (!searchRes.MediaContainer.Metadata) { + return interaction.reply({ content: `❌ | Ooops... something went wrong, couldn't find the song with the requested query.`, ephemeral: true }) + } + + //Otherwise it has found so defer reply + await interaction.deferReply(); + + //If there is more than one search result + if (searchRes.MediaContainer.size >= 2) { + var foundItems = [] + let count = 1 + //console.log(searchRes) + + var actionmenu = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("plexsearch") + .setMinValues(1) + .setMaxValues(1) + .setPlaceholder('Add an item to queue 👈') + //.addOptions(options) + ) + + for (var result of searchRes.MediaContainer.Metadata) { + //console.log(result) + foundItems.push({ name: `[${count}] ${result.type.charAt(0).toUpperCase() + result.type.slice(1)}`, value: `${result.title}` }) + + actionmenu.components[0].addOptions( + new StringSelectMenuOptionBuilder() + .setLabel(result.title) + .setValue(`${result.type}_${result.key}`) + .setDescription(`Duration - ${Math.floor(result.duration / 60000)}:${((result.duration % 60000) / 1000).toFixed(0)}`) + .setEmoji('🎵') + ) + + count++ + } + + const searchembed = new EmbedBuilder() + .setAuthor({ name: interaction.client.user.tag, iconURL: interaction.client.user.displayAvatarURL() }) + .setThumbnail(interaction.guild.iconURL({dynamic: true})) + .setTitle(`Plex Search Results 🎵`) + .setDescription('Found multiple songs matching the provided search query, select one form the menu below.') + .addFields(foundItems) + .setColor(process.env.EMBED_COLOUR) + .setTimestamp() + .setFooter({ text: `Requested by: ${interaction.user.tag}` }) + + interaction.followUp({ embeds: [searchembed], components: [actionmenu] }) + } + + //There is only one search result, play it direct + else { + var songFound = await searchRes.MediaContainer.Metadata[0] + + if (songFound.type == 'playlist') { + var playlistID = songFound.ratingKey + const search = await fetch(`${client.config.plexServer}/playlists/${playlistID}/items?X-Plex-Token=${client.config.plexAuthtoken}`, { + method: 'GET', + headers: { accept: 'application/json'} + }) + + var searchRes = await search.json() + + for await (var result of searchRes.MediaContainer.Metadata) { + //console.log(result) + var newTrack = new Track(player, { + title: result.title, + author: result.grandparentTitle, + url: `${client.config.plexServer}${result.Media[0].Part[0].key}?download=1&X-Plex-Token=${client.config.plexAuthtoken}`, + thumbnail: `${client.config.plexServer}${result.thumb}?download=1&X-Plex-Token=${client.config.plexAuthtoken}`, + duration: `${Math.floor(result.duration / 60000)}:${((result.duration % 60000) / 1000).toFixed(0)}`, + views: '69', + playlist: null, + description: null, + requestedBy: interaction.user, + source: 'arbitrary', + engine: `${client.config.plexServer}${result.Media[0].Part[0].key}?download=1&X-Plex-Token=${client.config.plexAuthtoken}`, + queryType: QueryType.ARBITRARY + }) + + try { + queue.addTrack(newTrack) + } + + catch (err) { + return interaction.followUp({ content: `❌ | Ooops... something went wrong, failed to add the track(s) to the queue.`, ephemeral: true }) + } + } + + var coverImage = new AttachmentBuilder(`${client.config.plexServer}${searchRes.MediaContainer.Metadata[0].thumb}?download=1&X-Plex-Token=${client.config.plexAuthtoken}`, { name: 'coverimage.jpg', description: `Song Cover Image for ${searchRes.MediaContainer.Metadata[0].title}` }) + } + + else { + var newTrack = new Track(player, { + title: songFound.title, + author: songFound.grandparentTitle, + url: `${client.config.plexServer}${songFound.Media[0].Part[0].key}?download=1&X-Plex-Token=${client.config.plexAuthtoken}`, + thumbnail: `${client.config.plexServer}${songFound.thumb}?download=1&X-Plex-Token=${client.config.plexAuthtoken}`, + duration: `${Math.floor(songFound.duration / 60000)}:${((songFound.duration % 60000) / 1000).toFixed(0)}`, + views: '69', + playlist: null, + description: null, + requestedBy: interaction.user, + source: 'arbitrary', + engine: `${client.config.plexServer}${songFound.Media[0].Part[0].key}?download=1&X-Plex-Token=${client.config.plexAuthtoken}`, + queryType: QueryType.ARBITRARY + }) + + try { + queue.addTrack(newTrack) + } + + catch (err) { + return interaction.followUp({ content: `❌ | Ooops... something went wrong, failed to add the track(s) to the queue.`, ephemeral: true }) + } + + var coverImage = new AttachmentBuilder(`${client.config.plexServer}${songFound.thumb}?download=1&X-Plex-Token=${client.config.plexAuthtoken}`, { name: 'coverimage.jpg', description: `Song Cover Image for ${songFound.title}` }) + } + + try { + if (!queue.connection) await queue.connect(interaction.member.voice.channel); + } + + catch (err) { + queue.delete(); + return interaction.followUp({ content: `❌ | Ooops... something went wrong, couldn't join your channel.`, ephemeral: true }) + } + //console.log(songFound.Media[0].Part[0]) + //console.log(`${client.config.plexServer}${songFound.Media[0].Part[0].key}?download=1&X-Plex-Token=${client.config.plexAuthtoken}`) + + if (!queue.isPlaying()) { + try { + await queue.node.play(queue.tracks[0]); + queue.node.setVolume(client.config.defaultVolume); + } + + catch (err) { + return interaction.followUp({ content: `❌ | Ooops... something went wrong, there was a playback related error. Please try again.`, ephemeral: true }) + } + + if (songFound.type == 'playlist') { + const playsongembed = new EmbedBuilder() + .setAuthor({ name: interaction.client.user.tag, iconURL: interaction.client.user.displayAvatarURL() }) + .setThumbnail('attachment://coverimage.jpg') + .setColor(process.env.EMBED_COLOUR) + .setTitle(`Started playback ▶️`) + .setDescription(`Imported the **${searchRes.MediaContainer.title} playlist** with **${searchRes.MediaContainer.size}** songs and started to play the queue!`) + .setTimestamp() + .setFooter({ text: `Requested by: ${interaction.user.tag}` }) + + interaction.followUp({ embeds: [playsongembed], files: [coverImage] }) + } + + else { + const playsongembed = new EmbedBuilder() + .setAuthor({ name: interaction.client.user.tag, iconURL: interaction.client.user.displayAvatarURL() }) + .setThumbnail('attachment://coverimage.jpg') + .setColor(process.env.EMBED_COLOUR) + .setTitle(`Started playback ▶️`) + .setDescription(`Began playing the song **${newTrack.title}**!`) + .setTimestamp() + .setFooter({ text: `Requested by: ${interaction.user.tag}` }) + + interaction.followUp({ embeds: [playsongembed], files: [coverImage] }) + } + } + + else { + if (songFound.type == 'playlist') { + const queuesongembed = new EmbedBuilder() + .setAuthor({ name: interaction.client.user.tag, iconURL: interaction.client.user.displayAvatarURL() }) + .setThumbnail('attachment://coverimage.jpg') + .setColor(process.env.EMBED_COLOUR) + .setTitle(`Added to queue ⏱️`) + .setDescription(`Imported the **${searchRes.MediaContainer.title} playlist** with **${searchRes.MediaContainer.size}** songs!`) + .setTimestamp() + .setFooter({ text: `Requested by: ${interaction.user.tag}` }) + + interaction.followUp({ embeds: [queuesongembed], files: [coverImage] }) + } + + else { + const queuesongembed = new EmbedBuilder() + .setAuthor({ name: interaction.client.user.tag, iconURL: interaction.client.user.displayAvatarURL() }) + .setThumbnail('attachment://coverimage.jpg') + .setColor(process.env.EMBED_COLOUR) + .setTitle(`Added to queue ⏱️`) + .setDescription(`Added song **${newTrack.title}** to the queue!`) + .setTimestamp() + .setFooter({ text: `Requested by: ${interaction.user.tag}` }) + + interaction.followUp({ embeds: [queuesongembed], files: [coverImage] }) + } + } + } + } + + catch (err) { + console.log(err) + return interaction.followUp({ content: `❌ | Ooops... something went wrong whilst attempting to play the requested song. Please try again.`, ephemeral: true }) + } + } + + else if (interaction.options.getSubcommand() === "search") { + if (process.env.ENABLE_DJMODE == true) { + if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + } + + if (!client.config.enablePlex) { + return interaction.reply({ content: `❌ | Plex is currently disabled! Ask the server admin to enable and configure this in the environment file.`, ephemeral: true }); + } + + await interaction.deferReply(); + if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + + const player = Player.singleton(); + const query = interaction.options.getString("music"); + + const fetch = require('node-fetch'); + try { + const search = await fetch(`${client.config.plexServer}/search/?X-Plex-Token=${client.config.plexAuthtoken}&query=${query}&limit=10&type=10,15`, { + method: 'GET', + headers: { accept: 'application/json'} + }) + + var searchRes = await search.json() + var foundItems = [] + let count = 1 + //console.log(searchRes) + + var actionmenu = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("plexsearch") + .setMinValues(1) + .setMaxValues(1) + .setPlaceholder('Add an item to queue 👈') + //.addOptions(options) + ) + + for (var result of searchRes.MediaContainer.Metadata) { + //console.log(result) + foundItems.push({ name: `[${count}] ${result.type.charAt(0).toUpperCase() + result.type.slice(1)}`, value: `${result.title}` }) + + actionmenu.components[0].addOptions( + new StringSelectMenuOptionBuilder() + .setLabel(result.title) + .setValue(`${result.type}_${result.key}`) + .setDescription(`Duration - ${Math.floor(result.duration / 60000)}:${((result.duration % 60000) / 1000).toFixed(0)}`) + .setEmoji('🎵') + ) + + count++ + } + + const searchembed = new EmbedBuilder() + .setAuthor({ name: interaction.client.user.tag, iconURL: interaction.client.user.displayAvatarURL() }) + .setThumbnail(interaction.guild.iconURL({dynamic: true})) + .setTitle(`Plex Search Results 🎵`) + .addFields(foundItems) + .setColor(process.env.EMBED_COLOUR) + .setTimestamp() + .setFooter({ text: `Requested by: ${interaction.user.tag}` }) + + interaction.followUp({ embeds: [searchembed], components: [actionmenu] }) + } + + catch (err) { + //console.log(err) + return interaction.followUp({ content: `❌ | Ooops... something went wrong whilst attempting to play the requested song. Please try again.`, ephemeral: true }) + } + } + } +} + +client.on('interactionCreate', async (interaction) => { + if (!interaction.isStringSelectMenu()) return; + if (interaction.customId == "plexsearch") { + await interaction.deferReply(); + + const player = Player.singleton(); + var checkqueue = player.nodes.get(interaction.guild.id); + + if (!checkqueue) { + player.nodes.create(interaction.guild.id, { + leaveOnEmpty: client.config.leaveOnEmpty, + leaveOnEmptyCooldown: client.config.leaveOnEmptyCooldown, + leaveOnEnd: client.config.leaveOnEnd, + leaveOnEndCooldown: client.config.leaveOnEndCooldown, + leaveOnStop: client.config.leaveOnStop, + leaveOnStopCooldown: client.config.leaveOnStopCooldown, + selfDeaf: client.config.selfDeafen, + skipOnNoStream: true, + metadata: { + channel: interaction.channel, + requestedBy: interaction.user, + client: interaction.guild.members.me, + } + }) + } + + var queue = player.nodes.get(interaction.guild.id); + const fetch = require('node-fetch'); + var allcomponents = interaction.values; + //console.log(allcomponents) + + for await (option of allcomponents) { + var getItemType = option.split('_')[0] + var getItemKey = option.split('_')[1] + + //Playlist + if (getItemType == 'playlist') { + const search = await fetch(`${client.config.plexServer}${getItemKey}?X-Plex-Token=${client.config.plexAuthtoken}`, { + method: 'GET', + headers: { accept: 'application/json'} + }) + + var searchRes = await search.json() + //console.log(searchRes) + + for await (var result of searchRes.MediaContainer.Metadata) { + //console.log(result) + var newTrack = new Track(player, { + title: result.title, + author: result.grandparentTitle, + url: `${client.config.plexServer}${result.Media[0].Part[0].key}?download=1&X-Plex-Token=${client.config.plexAuthtoken}`, + thumbnail: `${client.config.plexServer}${result.thumb}?download=1&X-Plex-Token=${client.config.plexAuthtoken}`, + duration: `${Math.floor(result.duration / 60000)}:${((result.duration % 60000) / 1000).toFixed(0)}`, + views: '69', + playlist: null, + description: null, + requestedBy: interaction.user, + source: 'arbitrary', + engine: `${client.config.plexServer}${result.Media[0].Part[0].key}?download=1&X-Plex-Token=${client.config.plexAuthtoken}`, + queryType: QueryType.ARBITRARY + }) + + try { + queue.addTrack(newTrack) + + } + + catch (err) { + return interaction.followUp({ content: `❌ | Ooops... something went wrong, failed to add the track(s) to the queue.`, ephemeral: true }) + } + } + + var coverImage = new AttachmentBuilder(`${client.config.plexServer}${searchRes.MediaContainer.Metadata[0].thumb}?download=1&X-Plex-Token=${client.config.plexAuthtoken}`, { name: 'coverimage.jpg', description: `Song Cover Image for ${searchRes.MediaContainer.Metadata[0].title}` }) + } + + //Single song + else { + const search = await fetch(`${client.config.plexServer}${getItemKey}?X-Plex-Token=${client.config.plexAuthtoken}`, { + method: 'GET', + headers: { accept: 'application/json'} + }) + + var searchRes = await search.json() + //console.log(searchRes) + + var songFound = await searchRes.MediaContainer.Metadata[0] + const newTrack = new Track(player, { + title: songFound.title, + author: songFound.grandparentTitle, + url: `${client.config.plexServer}${songFound.Media[0].Part[0].key}?download=1&X-Plex-Token=${client.config.plexAuthtoken}`, + thumbnail: `${client.config.plexServer}${songFound.thumb}?download=1&X-Plex-Token=${client.config.plexAuthtoken}`, + duration: `${Math.floor(songFound.duration / 60000)}:${((songFound.duration % 60000) / 1000).toFixed(0)}`, + views: '69', + playlist: null, + description: null, + requestedBy: interaction.user, + source: 'arbitrary', + engine: `${client.config.plexServer}${songFound.Media[0].Part[0].key}?download=1&X-Plex-Token=${client.config.plexAuthtoken}`, + queryType: QueryType.ARBITRARY + }) + + try { + queue.addTrack(newTrack) + } + + catch (err) { + return interaction.followUp({ content: `❌ | Ooops... something went wrong, failed to add the track(s) to the queue.`, ephemeral: true }) + } + + var coverImage = new AttachmentBuilder(`${client.config.plexServer}${songFound.thumb}?download=1&X-Plex-Token=${client.config.plexAuthtoken}`, { name: 'coverimage.jpg', description: `Song Cover Image for ${songFound.title}` }) + } + + try { + if (!queue.connection) await queue.connect(interaction.member.voice.channel); + } + + catch (err) { + queue.delete(); + return interaction.followUp({ content: `❌ | Ooops... something went wrong, couldn't join your channel.`, ephemeral: true }) + } + } + + if (!queue.isPlaying()) { + try { + await queue.node.play(queue.tracks[0]); + queue.node.setVolume(client.config.defaultVolume); + } + + catch (err) { + return interaction.followUp({ content: `❌ | Ooops... something went wrong, there was a playback related error. Please try again.`, ephemeral: true }) + } + + if (getItemType == 'playlist') { + const playsongembed = new EmbedBuilder() + .setAuthor({ name: interaction.client.user.tag, iconURL: interaction.client.user.displayAvatarURL() }) + .setThumbnail('attachment://coverimage.jpg') + .setColor(process.env.EMBED_COLOUR) + .setTitle(`Started playback ▶️`) + .setDescription(`Imported the **${searchRes.MediaContainer.title} playlist** with **${searchRes.MediaContainer.size}** songs and started to play the queue!`) + .setTimestamp() + .setFooter({ text: `Requested by: ${interaction.user.tag}` }) + + var sourceMessage = interaction.message + sourceMessage.edit({embeds: sourceMessage.embeds, components: []}) + interaction.followUp({ embeds: [playsongembed], files: [coverImage] }) + } + + else { + const playsongembed = new EmbedBuilder() + .setAuthor({ name: interaction.client.user.tag, iconURL: interaction.client.user.displayAvatarURL() }) + .setThumbnail('attachment://coverimage.jpg') + .setColor(process.env.EMBED_COLOUR) + .setTitle(`Started playback ▶️`) + .setDescription(`Began playing the song **${songFound.title}**!`) + .setTimestamp() + .setFooter({ text: `Requested by: ${interaction.user.tag}` }) + + var sourceMessage = interaction.message + sourceMessage.edit({embeds: sourceMessage.embeds, components: []}) + interaction.followUp({ embeds: [playsongembed], files: [coverImage] }) + } + } + + else { + if (getItemType == 'playlist') { + const playsongembed = new EmbedBuilder() + .setAuthor({ name: interaction.client.user.tag, iconURL: interaction.client.user.displayAvatarURL() }) + .setThumbnail('attachment://coverimage.jpg') + .setColor(process.env.EMBED_COLOUR) + .setTitle(`Added to queue ⏱️`) + .setDescription(`Imported the **${searchRes.MediaContainer.title} playlist** with **${searchRes.MediaContainer.size}** songs!`) + .setTimestamp() + .setFooter({ text: `Requested by: ${interaction.user.tag}` }) + + var sourceMessage = interaction.message + sourceMessage.edit({embeds: sourceMessage.embeds, components: []}) + interaction.followUp({ embeds: [playsongembed], files: [coverImage] }) + } + + else { + const playsongembed = new EmbedBuilder() + .setAuthor({ name: interaction.client.user.tag, iconURL: interaction.client.user.displayAvatarURL() }) + .setThumbnail('attachment://coverimage.jpg') + .setColor(process.env.EMBED_COLOUR) + .setTitle(`Added to queue ⏱️`) + .setDescription(`Added song **${songFound.title}** to the queue!`) + .setTimestamp() + .setFooter({ text: `Requested by: ${interaction.user.tag}` }) + + var sourceMessage = interaction.message + sourceMessage.edit({embeds: sourceMessage.embeds, components: []}) + interaction.followUp({ embeds: [playsongembed], files: [coverImage] }) + } + } + } +}) \ No newline at end of file diff --git a/commands/music/queue.js b/commands/music/queue.js index a21b9b2..e37b28c 100644 --- a/commands/music/queue.js +++ b/commands/music/queue.js @@ -1,19 +1,19 @@ require("dotenv").config(); const { SlashCommandBuilder } = require("@discordjs/builders"); -const { EmbedBuilder, PermissionFlagsBits, ButtonBuilder, ActionRowBuilder } = require("discord.js"); -const { Player, QueryType } = require('discord-player'); +const { EmbedBuilder, ButtonBuilder, ActionRowBuilder } = require("discord.js"); +const { Player } = require('discord-player'); module.exports = { data: new SlashCommandBuilder() .setName("queue") .setDescription("Check the current music that is in the queue!"), async execute(interaction) { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); @@ -34,11 +34,11 @@ module.exports = { var i = (curPage * 10) - 10; var curTracks = []; - curTracks.push({ name: 'Now Playing ▶️', value: `**${queue.currentTrack.title}** ([Link](${queue.currentTrack.url}))` },) + curTracks.push({ name: 'Now Playing ▶️', value: `**${queue.currentTrack.title}** ${queue.currentTrack.queryType != 'arbitrary' ? `([Link](${queue.currentTrack.url}))` : ''}` },) for (i; i < curPage * 10; i++) { if (queuedTracks[i]) { - curTracks.push({ name: `${i + 1}. ${queuedTracks[i].title}`, value: `**${queuedTracks[i].author}** ([Link](${queuedTracks[i].url}))` },) + curTracks.push({ name: `${i + 1}. ${queuedTracks[i].title}`, value: `**${queuedTracks[i].author}** ${queuedTracks[i].queryType != 'arbitrary' ? `([Link](${queuedTracks[i].url}))` : ''}` },) } } @@ -101,11 +101,11 @@ module.exports = { var i = (curPage * 10) - 10 var curTracks = [] - curTracks.push({ name: 'Now Playing ▶️', value: `**${queue.currentTrack.title}** ([Link](${queue.currentTrack.url}))` },) + curTracks.push({ name: 'Now Playing ▶️', value: `**${queue.currentTrack.title}** ${queue.currentTrack.queryType != 'arbitrary' ? `([Link](${queue.currentTrack.url}))` : ''}` },) for (i; i < curPage * 10; i++) { if (queuedTracks[i]) { - curTracks.push({ name: `${i + 1}. ${queuedTracks[i].title}`, value: `**${queuedTracks[i].author}** ([Link](${queuedTracks[i].url}))` },) + curTracks.push({ name: `${i + 1}. ${queuedTracks[i].title}`, value: `**${queuedTracks[i].author}** ${queuedTracks[i].queryType != 'arbitrary' ? `([Link](${queuedTracks[i].url}))` : ''}` },) } } diff --git a/commands/music/queuehistory.js b/commands/music/queuehistory.js index fa717b9..a223291 100644 --- a/commands/music/queuehistory.js +++ b/commands/music/queuehistory.js @@ -1,19 +1,19 @@ require("dotenv").config(); const { SlashCommandBuilder } = require("@discordjs/builders"); -const { EmbedBuilder, PermissionFlagsBits, ButtonBuilder, ActionRowBuilder } = require("discord.js"); -const { Player, QueryType } = require('discord-player'); +const { EmbedBuilder, ButtonBuilder, ActionRowBuilder } = require("discord.js"); +const { Player } = require('discord-player'); module.exports = { data: new SlashCommandBuilder() .setName("queuehistory") .setDescription("Check the past history of the queue within the guild!"), async execute(interaction) { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); @@ -36,11 +36,11 @@ module.exports = { var i = (curPage * 10) - 10; var curTracks = []; - curTracks.push({ name: 'Now Playing ▶️', value: `**${queue.currentTrack.title}** ([Link](${queue.currentTrack.url}))` },) + curTracks.push({ name: 'Now Playing ▶️', value: `**${queue.currentTrack.title}** ${queue.currentTrack.queryType != 'arbitrary' ? `([Link](${queue.currentTrack.url}))` : ''}` },) for (i; i < curPage * 10; i++) { if (previousTracks[i]) { - curTracks.push({ name: `${i + 1}. ${previousTracks[i].title}`, value: `**${previousTracks[i].author}** ([Link](${previousTracks[i].url}))` },) + curTracks.push({ name: `${i + 1}. ${previousTracks[i].title}`, value: `**${previousTracks[i].author}** ${previousTracks[i].queryType != 'arbitrary' ? `([Link](${previousTracks[i].url}))` : ''}` },) } } @@ -103,11 +103,11 @@ module.exports = { var i = (curPage * 10) - 10 var curTracks = [] - curTracks.push({ name: 'Now Playing ▶️', value: `**${queue.currentTrack.title}** ([Link](${queue.currentTrack.url}))` },) + curTracks.push({ name: 'Now Playing ▶️', value: `**${queue.currentTrack.title}** ${queue.currentTrack.queryType != 'arbitrary' ? `([Link](${queue.currentTrack.url}))` : ''}` },) for (i; i < curPage * 10; i++) { if (previousTracks[i]) { - curTracks.push({ name: `${i + 1}. ${previousTracks[i].title}`, value: `**${previousTracks[i].author}** ([Link](${previousTracks[i].url}))` },) + curTracks.push({ name: `${i + 1}. ${previousTracks[i].title}`, value: `**${previousTracks[i].author}** ${previousTracks[i].queryType != 'arbitrary' ? `([Link](${previousTracks[i].url}))` : ''}` },) } } diff --git a/commands/music/remove.js b/commands/music/remove.js index 395d8b8..0601dc5 100644 --- a/commands/music/remove.js +++ b/commands/music/remove.js @@ -1,7 +1,7 @@ require("dotenv").config(); const { SlashCommandBuilder } = require("@discordjs/builders"); -const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); -const { Player, QueryType } = require('discord-player'); +const { EmbedBuilder } = require("discord.js"); +const { Player } = require('discord-player'); module.exports = { data: new SlashCommandBuilder() @@ -14,12 +14,12 @@ module.exports = { ), async execute(interaction) { const removeamount = interaction.options.getInteger("song"); - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); @@ -33,7 +33,7 @@ module.exports = { .setThumbnail(interaction.guild.iconURL({dynamic: true})) .setColor(process.env.EMBED_COLOUR) .setTitle(`Song removed ❌`) - .setDescription(`Removed track ${queuedTracks[removeamount-1].title} ([Link](${queuedTracks[removeamount-1].url})) from the queue!`) + .setDescription(`Removed track ${queuedTracks[removeamount-1].title} ${queuedTracks[removeamount-1].queryType != 'arbitrary' ? `([Link](${queuedTracks[removeamount-1].url}))` : ''} from the queue!`) .setTimestamp() .setFooter({ text: `Requested by: ${interaction.user.tag}` }) diff --git a/commands/music/seek.js b/commands/music/seek.js index 0516d2b..4b37c62 100644 --- a/commands/music/seek.js +++ b/commands/music/seek.js @@ -1,7 +1,7 @@ require("dotenv").config(); const { SlashCommandBuilder } = require("@discordjs/builders"); -const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); -const { Player, QueryType } = require('discord-player'); +const { EmbedBuilder } = require("discord.js"); +const { Player } = require('discord-player'); const ms = require("ms"); module.exports = { @@ -14,12 +14,12 @@ module.exports = { .setRequired(true) ), async execute(interaction) { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); @@ -32,7 +32,7 @@ module.exports = { .setThumbnail(interaction.guild.iconURL({dynamic: true})) .setColor(process.env.EMBED_COLOUR) .setTitle(`Seek song ↪️`) - .setDescription(`Seeked the current song to ${ms(removeamount)}! Currently playing ${queue.currentTrack.title} ([Link](${queue.currentTrack.url})).`) + .setDescription(`Seeked the current song to ${ms(removeamount)}! Currently playing ${queue.currentTrack.title} ${queue.currentTrack.queryType != 'arbitrary' ? `([Link](${queue.currentTrack.url}))` : ''}.`) .setTimestamp() .setFooter({ text: `Requested by: ${interaction.user.tag}` }) diff --git a/commands/music/shuffle.js b/commands/music/shuffle.js index 39e9d93..2fa43bc 100644 --- a/commands/music/shuffle.js +++ b/commands/music/shuffle.js @@ -1,19 +1,19 @@ require("dotenv").config(); const { SlashCommandBuilder } = require("@discordjs/builders"); -const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); -const { Player, QueryType } = require('discord-player'); +const { EmbedBuilder } = require("discord.js"); +const { Player } = require('discord-player'); module.exports = { data: new SlashCommandBuilder() .setName("shuffle") .setDescription("Shuffle the current queue!"), async execute(interaction) { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); diff --git a/commands/music/skip.js b/commands/music/skip.js index 6ec0364..134c458 100644 --- a/commands/music/skip.js +++ b/commands/music/skip.js @@ -1,19 +1,19 @@ require("dotenv").config(); const { SlashCommandBuilder } = require("@discordjs/builders"); -const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); -const { Player, QueryType } = require('discord-player'); +const { EmbedBuilder, AttachmentBuilder } = require("discord.js"); +const { Player } = require('discord-player'); module.exports = { data: new SlashCommandBuilder() .setName("skip") .setDescription("Skip the current song!"), async execute(interaction) { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); @@ -22,18 +22,19 @@ module.exports = { const queuedTracks = queue.tracks.toArray(); if (!queuedTracks[0]) return interaction.reply({ content: `❌ | There is no music is currently in the queue!`, ephemeral: true }); + var coverImage = new AttachmentBuilder(queuedTracks[0].thumbnail, { name: 'coverimage.jpg', description: `Song Cover Image for ${queuedTracks[0].title}` }) const skipembed = new EmbedBuilder() .setAuthor({ name: interaction.client.user.tag, iconURL: interaction.client.user.displayAvatarURL() }) - .setThumbnail(queuedTracks[0].thumbnail) + .setThumbnail('attachment://coverimage.jpg') .setColor(process.env.EMBED_COLOUR) .setTitle(`Song skipped ⏭️`) - .setDescription(`Now playing: ${queuedTracks[0].title} ([Link](${queuedTracks[0].url}))`) + .setDescription(`Now playing: ${queuedTracks[0].title} ${queuedTracks[0].queryType != 'arbitrary' ? `([Link](${queuedTracks[0].url}))` : ''}`) .setTimestamp() .setFooter({ text: `Requested by: ${interaction.user.tag}` }) try { queue.node.skip(); - interaction.reply({ embeds: [skipembed] }); + interaction.reply({ embeds: [skipembed], files: [coverImage] }); } catch (err) { diff --git a/commands/music/stop.js b/commands/music/stop.js index 4f431d6..6ebd456 100644 --- a/commands/music/stop.js +++ b/commands/music/stop.js @@ -1,19 +1,19 @@ require("dotenv").config(); const { SlashCommandBuilder } = require("@discordjs/builders"); -const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); -const { Player, QueryType } = require('discord-player'); +const { EmbedBuilder } = require("discord.js"); +const { Player } = require('discord-player'); module.exports = { data: new SlashCommandBuilder() .setName("stop") .setDescription("Stops any music playing!"), async execute(interaction) { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); diff --git a/commands/music/volume.js b/commands/music/volume.js index 1e857cb..3f043d1 100644 --- a/commands/music/volume.js +++ b/commands/music/volume.js @@ -1,7 +1,7 @@ require("dotenv").config(); const { SlashCommandBuilder } = require("@discordjs/builders"); -const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); -const { Player, QueryType } = require('discord-player'); +const { EmbedBuilder } = require("discord.js"); +const { Player } = require('discord-player'); module.exports = { data: new SlashCommandBuilder() @@ -14,12 +14,12 @@ module.exports = { ), async execute(interaction) { const vol = interaction.options.getInteger("amount"); - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); diff --git a/commands/utilities/botinfo.js b/commands/utilities/botinfo.js index 70d99dd..39763c2 100644 --- a/commands/utilities/botinfo.js +++ b/commands/utilities/botinfo.js @@ -1,5 +1,5 @@ const { SlashCommandBuilder, inlineCode } = require("@discordjs/builders"); -const { EmbedBuilder, PermissionFlagsBits, ActionRowBuilder, ButtonBuilder } = require("discord.js"); +const { EmbedBuilder, ActionRowBuilder, ButtonBuilder } = require("discord.js"); module.exports = { data: new SlashCommandBuilder() diff --git a/commands/utilities/help.js b/commands/utilities/help.js index 87300c0..30a5a2c 100644 --- a/commands/utilities/help.js +++ b/commands/utilities/help.js @@ -1,5 +1,5 @@ const { SlashCommandBuilder } = require("@discordjs/builders"); -const { EmbedBuilder, PermissionFlagsBits, ActionRowBuilder, StringSelectMenuBuilder } = require("discord.js"); +const { EmbedBuilder, ActionRowBuilder, StringSelectMenuBuilder } = require("discord.js"); const fs = require("fs"); module.exports = { diff --git a/events/interactionCreate.js b/events/interactionCreate.js index a74d262..e368118 100644 --- a/events/interactionCreate.js +++ b/events/interactionCreate.js @@ -1,6 +1,6 @@ require("dotenv").config(); -const { EmbedBuilder, PermissionFlagsBits, ActionRowBuilder, ButtonBuilder, MessageSelectMenu, ModalBuilder, TextInputBuilder, TextInputStyle, AttachmentBuilder, Collection, StringSelectMenuBuilder } = require("discord.js"); -const { Player, QueueRepeatMode, QueryType } = require('discord-player'); +const { EmbedBuilder, PermissionFlagsBits, ActionRowBuilder, Collection, StringSelectMenuBuilder, AttachmentBuilder } = require("discord.js"); +const { Player, QueueRepeatMode } = require('discord-player'); const fs = require("fs"); const cooldowns = new Map(); @@ -229,8 +229,8 @@ module.exports = { //Check for button interactions else if (interaction.isButton()) { if (interaction.customId == "queue-delete") { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } interaction.message.delete() @@ -241,8 +241,8 @@ module.exports = { var queue = player.nodes.get(interaction.guild.id); if (!queue || !queue.isPlaying()) return interaction.reply({ content: `❌ | No music is currently being played!`, ephemeral: true }); - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } if (global.page == 1) return interaction.reply({ content: "❌ | The queue is already on the first page!", ephemeral: true }) @@ -262,7 +262,7 @@ module.exports = { .setColor(process.env.EMBED_COLOUR) .setTitle(`Current Music Queue 🎵`) .setDescription(`${musiclist.join('\n')}${queue.tracks.length > pageEnd ? `\n...and ${queue.tracks.length - pageEnd} more track(s)` : ''}`) - .addField('Now Playing ▶️', `**${currentMusic.title}** | ([Link](${currentMusic.url}))`) + .addField('Now Playing ▶️', `**${currentMusic.title}** ${currentMusic.queryType != 'arbitrary' ? `([Link](${currentMusic.url}))` : ''}`) .setTimestamp() .setFooter(`Requested by: ${interaction.user.tag}`) @@ -292,8 +292,8 @@ module.exports = { var queue = player.nodes.get(interaction.guild.id); if (!queue || !queue.isPlaying()) return interaction.reply({ content: `❌ | No music is currently being played!`, ephemeral: true }); - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } var pageStart = 10 * (page - 1); @@ -316,7 +316,7 @@ module.exports = { .setColor(process.env.EMBED_COLOUR) .setTitle(`Current Music Queue 🎵`) .setDescription(`${musiclist.join('\n')}${queue.tracks.length > pageEnd ? `\n...and ${queue.tracks.length - pageEnd} more track(s)` : ''}`) - .addField('Now Playing ▶️', `**${currentMusic.title}** | ([Link](${currentMusic.url}))`) + .addField('Now Playing ▶️', `**${currentMusic.title}** ${currentMusic.queryType != 'arbitrary' ? `([Link](${currentMusic.url}))` : ''}`) .setTimestamp() .setFooter(`Requested by: ${interaction.user.tag}`) @@ -342,20 +342,20 @@ module.exports = { } if (interaction.customId == "np-delete") { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } interaction.message.delete() } if (interaction.customId == "np-back") { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); @@ -369,7 +369,7 @@ module.exports = { .setThumbnail(interaction.guild.iconURL({dynamic: true})) .setColor(process.env.EMBED_COLOUR) .setTitle(`Playing previous song ⏮️`) - .setDescription(`Returning next to the previous song ${previousTracks[0].title} ([Link](${previousTracks[0].url}))!`) + .setDescription(`Returning next to the previous song ${previousTracks[0].title} ${queuedTracks[0].queryType != 'arbitrary' ? `([Link](${previousTracks[0].url}))` : ''}!`) .setTimestamp() .setFooter({ text: `Requested by: ${interaction.user.tag}` }) @@ -384,30 +384,31 @@ module.exports = { } if (interaction.customId == "np-pauseresume") { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); if (!queue || !queue.isPlaying()) return interaction.reply({ content: `❌ | No music is currently being played!`, ephemeral: true }); var checkPause = queue.node.isPaused(); + var coverImage = new AttachmentBuilder(queue.currentTrack.thumbnail, { name: 'coverimage.jpg', description: `Song Cover Image for ${queue.currentTrack.title}` }) const pauseembed = new EmbedBuilder() .setAuthor({ name: interaction.client.user.tag, iconURL: interaction.client.user.displayAvatarURL() }) - .setThumbnail(queue.currentTrack.thumbnail) + .setThumbnail('attachment://coverimage.jpg') .setColor(process.env.EMBED_COLOUR) .setTitle(`Song paused ⏸️`) - .setDescription(`Playback has been **${checkPause ? 'resumed' : 'paused'}**. Currently playing ${queue.currentTrack.title} ([Link](${queue.currentTrack.url}))!`) + .setDescription(`Playback has been **${checkPause ? 'resumed' : 'paused'}**. Currently playing ${queue.currentTrack.title} ${queue.currentTrack.queryType != 'arbitrary' ? `([Link](${queue.currentTrack.url}))` : ''}!`) .setTimestamp() .setFooter({ text: `Requested by: ${interaction.user.tag}` }) try { queue.node.setPaused(!queue.node.isPaused()); - interaction.reply({ embeds: [pauseembed] }) + interaction.reply({ embeds: [pauseembed], files: [coverImage] }) } catch (err) { @@ -416,12 +417,12 @@ module.exports = { } if (interaction.customId == "np-skip") { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); @@ -430,18 +431,19 @@ module.exports = { const queuedTracks = queue.tracks.toArray(); if (!queuedTracks[0]) return interaction.reply({ content: `❌ | There is no music is currently in the queue!`, ephemeral: true }); + var coverImage = new AttachmentBuilder(queuedTracks[0].thumbnail, { name: 'coverimage.jpg', description: `Song Cover Image for ${queuedTracks[0].title}` }) const skipembed = new EmbedBuilder() .setAuthor({ name: interaction.client.user.tag, iconURL: interaction.client.user.displayAvatarURL() }) - .setThumbnail(queuedTracks[0].thumbnail) + .setThumbnail('attachment://coverimage.jpg') .setColor(process.env.EMBED_COLOUR) .setTitle(`Song skipped ⏭️`) - .setDescription(`Now playing: ${queuedTracks[0].title} ([Link](${queuedTracks[0].url}))`) + .setDescription(`Now playing: ${queuedTracks[0].title} ${queuedTracks[0].queryType != 'arbitrary' ? `([Link](${queuedTracks[0].url}))` : ''}`) .setTimestamp() .setFooter({ text: `Requested by: ${interaction.user.tag}` }) try { queue.node.skip(); - interaction.reply({ embeds: [skipembed] }) + interaction.reply({ embeds: [skipembed], files: [coverImage] }) } catch (err) { @@ -450,12 +452,12 @@ module.exports = { } if (interaction.customId == "np-clear") { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); @@ -481,12 +483,12 @@ module.exports = { } if (interaction.customId == "np-volumeup") { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); @@ -515,12 +517,12 @@ module.exports = { } if (interaction.customId == "np-volumedown") { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); @@ -549,12 +551,12 @@ module.exports = { } if (interaction.customId == "np-loop") { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); @@ -596,12 +598,12 @@ module.exports = { } if (interaction.customId == "np-shuffle") { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); @@ -627,12 +629,12 @@ module.exports = { } if (interaction.customId == "np-stop") { - if (process.env.ENABLE_DJMODE == true) { - if (!interaction.member.roles.cache.has(process.env.DJ_ROLE)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${process.env.DJ_ROLE}> to use any music commands!`, ephemeral: true }); + if (client.config.enableDjMode) { + if (!interaction.member.roles.cache.has(client.config.djRole)) return interaction.reply({ content: `❌ | DJ Mode is active! You must have the DJ role <@&${client.config.djRole}> to use any music commands!`, ephemeral: true }); } - if (!interaction.member.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in a voice channel!", ephemeral: true }); - if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.followUp({ content: "❌ | You are not in my voice channel!", ephemeral: true }); + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "❌ | You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "❌ | You are not in my voice channel!", ephemeral: true }); const player = Player.singleton(); var queue = player.nodes.get(interaction.guild.id); diff --git a/events/musicevents.js b/events/musicevents.js index 3967687..ba333e4 100644 --- a/events/musicevents.js +++ b/events/musicevents.js @@ -1,5 +1,5 @@ -const { EmbedBuilder, PermissionFlagsBits, ButtonBuilder, ActionRowBuilder } = require("discord.js"); -const { Player, QueryType } = require('discord-player'); +const { EmbedBuilder, PermissionFlagsBits, ButtonBuilder, ActionRowBuilder, AttachmentBuilder } = require("discord.js"); +const { Player } = require('discord-player'); const player = Player.singleton(); player.events.on("error", (queue, error) => { @@ -14,14 +14,15 @@ player.events.on("playerError", (queue, error) => { player.events.on("playerStart", async (queue, track) => { //queue.metadata.channel.send(`🎶 | Started playing: **${track.title}** in **${queue.connection.channel.name}**!`); const progress = queue.node.createProgressBar(); - var create = progress.replace(/ 0:00/g, ' ◉ LIVE'); + var createBar = progress.replace(/ 0:00/g, ' ◉ LIVE'); + let coverImage = new AttachmentBuilder(queue.currentTrack.thumbnail, { name: 'coverimage.jpg', description: `Song Cover Image for ${queue.currentTrack.title}` }) const npembed = new EmbedBuilder() .setAuthor({ name: player.client.user.tag, iconURL: player.client.user.displayAvatarURL() }) - .setThumbnail(queue.currentTrack.thumbnail) + .setThumbnail('attachment://coverimage.jpg') .setColor(process.env.EMBED_COLOUR) .setTitle(`Starting next song... Now Playing 🎵`) - .setDescription(`${queue.currentTrack.title} ([Link](${queue.currentTrack.url}))\n${create}`) + .setDescription(`${queue.currentTrack.title} ${track.queryType != 'arbitrary' ? `([Link](${queue.currentTrack.url}))` : ''}\n${createBar}`) //.addField('\u200b', progress.replace(/ 0:00/g, ' ◉ LIVE')) .setTimestamp() @@ -79,7 +80,7 @@ player.events.on("playerStart", async (queue, track) => { //Check if bot has message perms if (!queue.guild.members.me.permissionsIn(queue.metadata.channel).has(PermissionFlagsBits.SendMessages)) return console.log(`No Perms! (ID: ${queue.guild.id})`); - var msg = await queue.metadata.channel.send({ embeds: [npembed], components: finalComponents }) + var msg = await queue.metadata.channel.send({ embeds: [npembed], components: finalComponents, files: [coverImage] }) //----- Dyanmic Button Removal (main drawback being efficiency, but benefit being that it will only remove buttons once the next songs begins, ensuring they always stay) ----- const filter = (collectorMsg) => { @@ -136,8 +137,8 @@ player.events.on("disconnect", (queue) => { .setAuthor({ name: player.client.user.tag, iconURL: player.client.user.displayAvatarURL() }) .setThumbnail(queue.guild.iconURL({dynamic: true})) .setColor(process.env.EMBED_COLOUR) - .setTitle(`Ending playback... 🛑`) - .setDescription(`I've been manually disconnected from the voice channel, clearing queue...!`) + .setTitle(`Disconnecting 🛑`) + .setDescription(`I've been inactive for a period of time!`) .setTimestamp() //Check if bot has message perms @@ -152,8 +153,8 @@ player.events.on("emptyChannel", (queue) => { .setAuthor({ name: player.client.user.tag, iconURL: player.client.user.displayAvatarURL() }) .setThumbnail(queue.guild.iconURL({dynamic: true})) .setColor(process.env.EMBED_COLOUR) - .setTitle(`Ending playback... 🛑`) - .setDescription(`Nobody is in the voice channel, disconnecting...!`) + .setTitle(`Ending playback 🛑`) + .setDescription(`Nobody is in the voice channel!`) .setTimestamp() //Check if bot has message perms @@ -168,8 +169,8 @@ player.events.on("emptyQueue", (queue) => { .setAuthor({ name: player.client.user.tag, iconURL: player.client.user.displayAvatarURL() }) .setThumbnail(queue.guild.iconURL({dynamic: true})) .setColor(process.env.EMBED_COLOUR) - .setTitle(`Ending playback... 🛑`) - .setDescription(`The music queue has been finished, disconnecting...!`) + .setTitle(`Queue Finished 🛑`) + .setDescription(`The music queue has been finished!`) .setTimestamp() //Check if bot has message perms diff --git a/events/ready.js b/events/ready.js index 48ee460..c63f337 100644 --- a/events/ready.js +++ b/events/ready.js @@ -10,7 +10,7 @@ module.exports = { const defaultConsts = require(`../utils/defaultConsts`); client.config = defaultConsts.config; - new Promise((resolve, reject) => { + new Promise(async (resolve, reject) => { client.config.embedColour = typeof (process.env.EMBED_COLOUR) === 'undefined' ? client.config.embedColour : ((/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i).test(process.env.EMBED_COLOUR) ? process.env.EMBED_COLOUR : client.config.embedColour); @@ -18,14 +18,6 @@ module.exports = { client.config.presence = typeof (process.env.PRESENCE) === 'undefined' ? client.config.presence : (String(process.env.PRESENCE) ? process.env.PRESENCE : client.config.presence); - - client.config.enableDjMode = typeof (process.env.ENABLE_DJMODE) === 'undefined' - ? client.config.enableDjMode - : (String(process.env.ENABLE_DJMODE) === 'true' ? true : false); - - client.config.djRole = typeof (process.env.DJ_ROLE) === 'undefined' - ? client.config.djRole - : (Number(process.env.DJ_ROLE) ? process.env.DJ_ROLE : client.config.djRole); client.config.leaveOnEmpty = typeof (process.env.LEAVE_ON_EMPTY) === 'undefined' ? client.config.leaveOnEmpty @@ -63,11 +55,66 @@ module.exports = { ? client.config.smoothVolume : (String(process.env.SMOOTH_VOLUME) === 'true' ? true : false); - console.log(`[ELITE_CONFIG] Configuration loading... Current config:\n${JSON.stringify(client.config, null, 3)}`) - console.log(`Note: If some configuration option is incorrect, please double check that it is correctly set within your .ENV file!\nOtherwise, where a configuraiton option is invalid, the default from defaultConsts.js will be used.`) + client.config.enableDjMode = typeof (process.env.ENABLE_DJMODE) === 'undefined' + ? client.config.enableDjMode + : (String(process.env.ENABLE_DJMODE) === 'true' ? true : false); + + client.config.djRole = typeof (process.env.DJ_ROLE) === 'undefined' + ? client.config.djRole + : (Number(process.env.DJ_ROLE) ? process.env.DJ_ROLE : client.config.djRole); + + client.config.enablePlex = typeof (process.env.ENABLE_PLEX) === 'undefined' + ? client.config.enablePlex + : (String(process.env.ENABLE_PLEX) === 'true' ? true : false); + + client.config.plexServer = typeof (process.env.PLEX_SERVER) === 'undefined' + ? client.config.plexServer + : (String(process.env.PLEX_SERVER) ? process.env.PLEX_SERVER : client.config.plexServer); + + client.config.plexAuthtoken = typeof (process.env.PLEX_AUTHTOKEN) === 'undefined' + ? client.config.plexAuthtoken + : (String(process.env.PLEX_AUTHTOKEN) ? process.env.PLEX_AUTHTOKEN : client.config.plexAuthtoken); + + //Perform validation checks + if (client.config.enablePlex) { + //Abort fetch after 3 seconds + const controller = new AbortController(); + setTimeout(() => controller.abort("Fetch aborted: Plex Server URL must be invalid as request received no response."), 3000); + + await fetch(`${client.config.plexServer}/search/?X-Plex-Token=${client.config.plexAuthtoken}&query=test&limit=1`, { + method: 'GET', + headers: { accept: 'application/json'}, + signal: controller.signal + }) + .then(search => { + //401 = Unauthorized, 404 = Not Found, 200 = OK + if (search.status == 401) { + console.log(`[ELITE_CONFIG] Plex configuration is invalid. Disabling Plex feature... Your Plex Authentication token is not valid.`) + client.config.enablePlex = false; + } + + else if (search.status != 200) { + console.log(`[ELITE_CONFIG] Plex configuration is invalid. Disabling Plex feature... Generic error.`) + client.config.enablePlex = false; + } + }) + .catch(err => { + if (controller.signal.aborted) { + console.log(`[ELITE_CONFIG] Plex configuration is invalid. Disabling Plex feature... Read more in the trace below:\n${controller.signal.reason}`) + } + + else { + console.log(`[ELITE_CONFIG] Plex configuration is invalid. Disabling Plex feature... Read more in the trace below:\n${err}`) + } + client.config.enablePlex = false; + }) + } resolve(); }) - - console.log("[ELITE_STATUS] Loading successful. Core of the bot is ready!"); + .then(() => { + console.log(`[ELITE_CONFIG] Configuration loaded... Current config:\n${JSON.stringify(client.config, null, 3)}`) + console.log(`Note: If some configuration option is incorrect, please double check that it is correctly set within your .ENV file!\nOtherwise, where a configuraiton option is invalid, the default from defaultConsts.js will be used.`) + console.log("\n[ELITE_STATUS] Loading successful. Core of the bot is ready!"); + }) } } \ No newline at end of file diff --git a/index.js b/index.js index 0a3520e..dc6d584 100644 --- a/index.js +++ b/index.js @@ -28,23 +28,22 @@ client = new Client({ process.on('uncaughtException', async function(err) { var date = new Date(); console.log(`Caught Exception: ${err.stack}\n`); - fs.appendFileSync('logs/exception.txt', `${date.toGMTString()}: ${err.stack}\n`); + fs.appendFileSync('exception.txt', `${date.toGMTString()}: ${err.stack}\n`); }); process.on('unhandledRejection', async function(err) { var date = new Date(); console.log(`Caught Rejection: ${err.stack}\n`); - fs.appendFileSync('logs/rejection.txt', `${date.toGMTString()}: ${err.stack}\n`); + fs.appendFileSync('rejection.txt', `${date.toGMTString()}: ${err.stack}\n`); }); //Discord-Player initialisation const defaultConsts = require(`./utils/defaultConsts`); -const { YouTubeExtractor, SpotifyExtractor, SoundCloudExtractor, AttachmentExtractor } = require('@discord-player/extractor') const player = new Player(client, { smoothVolume: process.env.SMOOTH_VOLUME, ytdlOptions: defaultConsts.ytdlOptions }) -player.extractors.register(YouTubeExtractor, SpotifyExtractor, SoundCloudExtractor, AttachmentExtractor) +player.extractors.loadDefault(); //Initialise commands through JSON const commands = []; @@ -61,6 +60,7 @@ fs.readdirSync("./commands/").forEach((dir) => { //Register all of the commands client.once('ready', async function() { + console.log('[ELITE_CONFIG] Loading Configuration...') const rest = new REST({ version: "10" }).setToken(process.env.TOKEN); try { @@ -91,5 +91,5 @@ for (const file of eventFiles) { //For each file, check if the event is .once or //Login to the bot via token passed (from .env) client.login(process.env.TOKEN) .catch((err) => { - console.log(`[ELITE_ERROR] Bot could not login and authenticate.\nError Trace: ${err}`); + console.log(`[ELITE_ERROR] Bot could not login and authenticate with Discord.\nError Trace: ${err}`); }) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5ede3fe..be11cb5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,12 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@discord-player/extractor": "^4.4.1", "@discordjs/opus": "0.9.0", "@discordjs/rest": "1.7.1", "@distube/ytdl-core": "4.11.10", "discord-api-types": "0.37.43", - "discord-player": "6.5.0", + "discord-player": "6.6.2", "discord.js": "14.11.0", "dotenv": "16.0.1", "ffmpeg-static": "5.1.0", @@ -42,18 +43,18 @@ } }, "node_modules/@discord-player/equalizer": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@discord-player/equalizer/-/equalizer-0.2.1.tgz", - "integrity": "sha512-PPjX8TBwzM85YKEdpYYL8aGbdSLIk9RK1h/uU5jeyiF6Uu5CiLH/XctDfcGW8cSgoaU4J0R6a8svCs9yhzHXSw==" + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@discord-player/equalizer/-/equalizer-0.2.2.tgz", + "integrity": "sha512-U86em1cMtopXVuKFDxP8J2DcNOqwCAsYQ2tkzPoJvchsrezcI2cgvN3jtGt9FFECP3pLvSz96ZB9FOHZKG5Mqg==" }, "node_modules/@discord-player/extractor": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@discord-player/extractor/-/extractor-4.3.1.tgz", - "integrity": "sha512-f8EwbQkplZQdVb4pCazc+5zo57y4gkPCl4Zq+3nHhwD+lz03OSmFGfoU6Ho9InBbx6S6PthO6CSPyFXN3sH6nw==", - "peer": true, + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@discord-player/extractor/-/extractor-4.4.1.tgz", + "integrity": "sha512-wOsSjU7nU682ZNniGC8AwhDZMMun7Bmkn3lrefa6lO5bUp6dACKdj9aLWlILDIMOohhhcBEPLXv5AtdYjumwHw==", "dependencies": { "file-type": "^16.5.4", "genius-lyrics": "^4.4.3", + "isomorphic-unfetch": "^4.0.2", "node-html-parser": "^6.1.4", "reverbnation-scraper": "^2.0.0", "soundcloud.ts": "^0.5.0", @@ -61,10 +62,15 @@ "youtube-sr": "^4.3.4" } }, + "node_modules/@discord-player/ffmpeg": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@discord-player/ffmpeg/-/ffmpeg-0.1.0.tgz", + "integrity": "sha512-0kW6q4gMQN2B4Z4EzmUgXrKQSXXmyhjdZBBZ/6jSHZ9fh814oOu+JXP01VvtWHwTylI7qJHIctEWtSyjEubCJg==" + }, "node_modules/@discord-player/utils": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@discord-player/utils/-/utils-0.2.1.tgz", - "integrity": "sha512-yPxfdO2N3i2YEEiwlLDNyuBEdnhV1mZaC7im2BI4FKn3ak1UAtVcbKeJhdd/va0A170+PZs3zUKVGldY0z/+ng==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@discord-player/utils/-/utils-0.2.2.tgz", + "integrity": "sha512-UklWUT7BcZEkBgywM9Cmpo2nwj3SQ9Wmhu6ml1uy/YRQnY8IRdZEHD84T2kfjOg4LVZek0ej1VerIqq7a9PAHQ==", "dependencies": { "@discordjs/collection": "^1.1.0" } @@ -475,8 +481,7 @@ "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "peer": true + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, "node_modules/brace-expansion": { "version": "1.1.11", @@ -524,7 +529,6 @@ "version": "1.0.0-rc.12", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", - "peer": true, "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", @@ -545,7 +549,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "peer": true, "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", @@ -645,7 +648,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "peer": true, "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", @@ -661,7 +663,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "peer": true, "engines": { "node": ">= 6" }, @@ -680,6 +681,14 @@ "node": ">=0.10" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -736,12 +745,13 @@ "integrity": "sha512-bBhDWU3TF9KADxR/mHp1K4Bvu/LRtFQdGyBjADu4e66F3ZnD4kp12W/SJCttIaCcMXzPV3sfty6eDGRNRph51Q==" }, "node_modules/discord-player": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/discord-player/-/discord-player-6.5.0.tgz", - "integrity": "sha512-rSS0kpbQyeIGF/NvkE+obpLnKP1as2Ca+7kvM/bU5Pp4I1DWyc8zwRhJMELRGrLDeXZk1wvHUFfFLVC587VqYw==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/discord-player/-/discord-player-6.6.2.tgz", + "integrity": "sha512-+hX+6r6K/kQZ0Ip8vxWpIC3zMZDdUFvlc4pqytO4LcGFrcqRM/Mba59o/fWP34tp4H3bB3wDo5LpKQfsKCjsPg==", "dependencies": { - "@discord-player/equalizer": "^0.2.1", - "@discord-player/utils": "^0.2.1", + "@discord-player/equalizer": "^0.2.2", + "@discord-player/ffmpeg": "^0.1.0", + "@discord-player/utils": "^0.2.2", "@discordjs/voice": "latest", "libsodium-wrappers": "^0.7.10" }, @@ -749,7 +759,7 @@ "url": "https://github.com/Androz2091/discord-player?sponsor=1" }, "peerDependencies": { - "@discord-player/extractor": "^4.3.1", + "@discord-player/extractor": "^4.4.1", "discord.js": "14.x", "youtube-sr": "4.x" } @@ -782,7 +792,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "peer": true, "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -801,14 +810,12 @@ "type": "github", "url": "https://github.com/sponsors/fb55" } - ], - "peer": true + ] }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "peer": true, "dependencies": { "domelementtype": "^2.3.0" }, @@ -823,7 +830,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", - "peer": true, "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -864,7 +870,6 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "peer": true, "engines": { "node": ">=0.12" }, @@ -903,6 +908,28 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/ffmpeg-static": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-5.1.0.tgz", @@ -922,7 +949,6 @@ "version": "16.5.4", "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", - "peer": true, "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", @@ -968,6 +994,17 @@ "node": ">= 0.12" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -1040,7 +1077,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/genius-lyrics/-/genius-lyrics-4.4.3.tgz", "integrity": "sha512-06L8GUg49FrUYEmSQvrSH74RH5S+qyerHwBpvk8vZLwWgpEw4mIWZDob5IpXT1ryhqazM9K6CXGNucKYPO8kng==", - "peer": true, "dependencies": { "cheerio": "^1.0.0-rc.9", "undici": "^5.8.2" @@ -1124,7 +1160,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "peer": true, "bin": { "he": "bin/he" } @@ -1132,8 +1167,7 @@ "node_modules/himalaya": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/himalaya/-/himalaya-1.1.0.tgz", - "integrity": "sha512-LLase1dHCRMel68/HZTFft0N0wti0epHr3nNY7ynpLbyZpmrKMQ8YIpiOV77TM97cNpC8Wb2n6f66IRggwdWPw==", - "peer": true + "integrity": "sha512-LLase1dHCRMel68/HZTFft0N0wti0epHr3nNY7ynpLbyZpmrKMQ8YIpiOV77TM97cNpC8Wb2n6f66IRggwdWPw==" }, "node_modules/htmlparser2": { "version": "8.0.2", @@ -1146,7 +1180,6 @@ "url": "https://github.com/sponsors/fb55" } ], - "peer": true, "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", @@ -1311,6 +1344,32 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, + "node_modules/isomorphic-unfetch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-4.0.2.tgz", + "integrity": "sha512-1Yd+CF/7al18/N2BDbsLBcp6RO3tucSW+jcLq24dqdX5MNbCNTw1z4BsGsp4zNmjr/Izm2cs/cEqZPp4kvWSCA==", + "dependencies": { + "node-fetch": "^3.2.0", + "unfetch": "^5.0.0" + } + }, + "node_modules/isomorphic-unfetch/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -1577,6 +1636,24 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { "version": "2.6.11", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", @@ -1600,7 +1677,6 @@ "version": "6.1.5", "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.5.tgz", "integrity": "sha512-fAaM511feX++/Chnhe475a0NHD8M7AxDInsqQpz6x63GRF7xYNdS8Vo5dKsIVPgsOvG7eioRRTZQnWBrhDHBSg==", - "peer": true, "dependencies": { "css-select": "^5.1.0", "he": "1.2.0" @@ -1690,7 +1766,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "peer": true, "dependencies": { "boolbase": "^1.0.0" }, @@ -1731,7 +1806,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "peer": true, "dependencies": { "entities": "^4.4.0" }, @@ -1743,7 +1817,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", - "peer": true, "dependencies": { "domhandler": "^5.0.2", "parse5": "^7.0.0" @@ -1764,7 +1837,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", - "peer": true, "engines": { "node": ">=8" }, @@ -1935,7 +2007,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/reverbnation-scraper/-/reverbnation-scraper-2.0.0.tgz", "integrity": "sha512-t1Mew5QC9QEVEry5DXyagvci2O+TgXTGoMHbNoW5NRz6LTOzK/DLHUpnrQwloX8CVX5z1a802vwHM3YgUVOvKg==", - "peer": true, "dependencies": { "node-fetch": "^2.6.0" } @@ -1984,9 +2055,9 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -2111,19 +2182,17 @@ } }, "node_modules/soundcloud.ts": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/soundcloud.ts/-/soundcloud.ts-0.5.0.tgz", - "integrity": "sha512-QZLJ0X+/hpj8/5rqTGnLl/U4d07qcobrfXrM3+VlCQcXG5v+7MZ6tVHDFPsDH0MWM8op541RwhvKc8JqWlyUHA==", - "peer": true, + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/soundcloud.ts/-/soundcloud.ts-0.5.2.tgz", + "integrity": "sha512-/pc72HWYJpSpup+mJBE9pT31JsrMcxJGBlip3Vem+0Fsscg98xh1/7I2nCpAKuMAeV6MVyrisI8TfjO6T7qKJg==", "dependencies": { - "undici": "^5.21.2" + "undici": "^5.22.1" } }, "node_modules/spotify-uri": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/spotify-uri/-/spotify-uri-3.0.4.tgz", "integrity": "sha512-wtofZNzMjPXR1KD2/gw8F/7ng1QwxpfFbrVgcRaAh0oSJ6ZGC5ln+IBptIRuti1dYGOxJqEIvDc88ctLvTSWiQ==", - "peer": true, "engines": { "node": ">= 16" } @@ -2132,7 +2201,6 @@ "version": "3.2.5", "resolved": "https://registry.npmjs.org/spotify-url-info/-/spotify-url-info-3.2.5.tgz", "integrity": "sha512-5LpA8PznECfTPRVcgYSyYOn7dr3aq+sHgl3JrDDKT7NVJ8exEhFD1NsmR8/kObc6qlr0H6t+AZDVVfv3HiEKaQ==", - "peer": true, "dependencies": { "himalaya": "~1.1.0", "spotify-uri": "~3.0.3" @@ -2217,7 +2285,6 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", - "peer": true, "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" @@ -2274,7 +2341,6 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", - "peer": true, "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" @@ -2384,6 +2450,11 @@ "node": ">=14.0" } }, + "node_modules/unfetch": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-5.0.0.tgz", + "integrity": "sha512-3xM2c89siXg0nHvlmYsQ2zkLASvVMBisZm5lF3gFDqfF2xonNStDJyMpvaOBe0a1Edxmqrf2E0HBdmy9QyZaeg==" + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -2427,6 +2498,14 @@ "extsprintf": "^1.2.0" } }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -2490,8 +2569,7 @@ "node_modules/youtube-sr": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/youtube-sr/-/youtube-sr-4.3.4.tgz", - "integrity": "sha512-olSYcR80XigutCrePEXBX3/RJJrWfonJQj7+/ggBiWU0CzTDLE1q8+lpWTWCG0JpzhzILp/IB/Bq/glGqqr1TQ==", - "peer": true + "integrity": "sha512-olSYcR80XigutCrePEXBX3/RJJrWfonJQj7+/ggBiWU0CzTDLE1q8+lpWTWCG0JpzhzILp/IB/Bq/glGqqr1TQ==" }, "node_modules/ytdl-core": { "version": "4.11.4", diff --git a/package.json b/package.json index 62008c3..1f32811 100644 --- a/package.json +++ b/package.json @@ -9,11 +9,12 @@ "author": "ThatGuyJacobee", "license": "ISC", "dependencies": { + "@discord-player/extractor": "^4.4.1", "@discordjs/opus": "0.9.0", "@discordjs/rest": "1.7.1", "@distube/ytdl-core": "4.11.10", "discord-api-types": "0.37.43", - "discord-player": "6.5.0", + "discord-player": "6.6.2", "discord.js": "14.11.0", "dotenv": "16.0.1", "ffmpeg-static": "5.1.0", diff --git a/utils/defaultConsts.js b/utils/defaultConsts.js index 0bfd43b..1f60474 100644 --- a/utils/defaultConsts.js +++ b/utils/defaultConsts.js @@ -3,8 +3,6 @@ const defaultConsts = { config: { embedColour: '#FF0000', presence: '/help | elite-bot.com', - enableDjMode: false, - djRole: 1234567891011, leaveOnEmpty: false, leaveOnEmptyCooldown: 0, leaveOnEnd: false, @@ -14,6 +12,11 @@ const defaultConsts = { selfDeafen: true, defaultVolume: 50, smoothVolume: true, + enableDjMode: false, + djRole: 1234567891011, + enablePlex: false, + plexServer: '', + plexAuthtoken: '' }, ytdlOptions: { filter: 'audioonly',