diff --git a/commands/Administrator/set.js b/commands/Administrator/set.js index 37a4c384..4883a20f 100644 --- a/commands/Administrator/set.js +++ b/commands/Administrator/set.js @@ -18,7 +18,7 @@ class Set extends Command { longDescription: 'Example: `-set edit welcomeEnabled true` will enable the welcome message.', category: 'Administrator', permLevel: 'Administrator', - usage: 'set ', + usage: 'set ', aliases: ['setting', 'settings'], guildOnly: true, }); diff --git a/commands/Music/leave-voice.js b/commands/Music/leave-voice.js deleted file mode 100644 index cfac7cc7..00000000 --- a/commands/Music/leave-voice.js +++ /dev/null @@ -1,24 +0,0 @@ -const Command = require('../../base/Command.js'); - -class LeaveVoice extends Command { - constructor(client) { - super(client, { - name: 'leave-voice', - description: 'Makes the bot leave the voice channel.', - category: 'Music', - usage: 'leave-voice', - aliases: ['leavevoice', 'lv'], - guildOnly: true, - }); - } - - async run(msg) { - if (!msg.member.voice.channel) return msg.channel.send('You must be in a voice channel to make the bot leave.'); - if (msg.guild.members.me.voice.channel && msg.member.voice.channel.id !== msg.guild.members.me.voice.channel.id) - return msg.channel.send('You must be in the same voice channel as the bot.'); - - return msg.guild.members.me.voice.disconnect(); - } -} - -module.exports = LeaveVoice; diff --git a/commands/Music/now-playing.js b/commands/Music/now-playing.js index 0a3ef635..03a1f5fa 100755 --- a/commands/Music/now-playing.js +++ b/commands/Music/now-playing.js @@ -24,11 +24,11 @@ class NowPlaying extends Command { const em = new EmbedBuilder() .setDescription( stripIndents` - Currently ${queue.node.isPlaying() ? 'Playing' : 'Paused'} ♪: [${song.title}](${song.url}) + Currently ${queue.node.isPlaying() ? 'Playing' : 'Paused'} ♪: [${song.title}](${song.url}) - ${queue.node.createProgressBar({ timecodes: true })} + ${queue.node.createProgressBar({ timecodes: true })} - Requested By: ${song.requestedBy} + Requested By: ${song.requestedBy} `, ) .setColor(msg.settings.embedColor) diff --git a/commands/Music/queue.js b/commands/Music/queue.js index 5d1a79a2..57d493dd 100755 --- a/commands/Music/queue.js +++ b/commands/Music/queue.js @@ -6,7 +6,7 @@ class Queue extends Command { constructor(client) { super(client, { name: 'queue', - description: 'Shows what is in the queue', + description: 'See what songs are in the queue', category: 'Music', usage: 'queue [page]', aliases: ['q'], diff --git a/commands/Music/remove.js b/commands/Music/remove.js index cda1151e..2a5735c7 100755 --- a/commands/Music/remove.js +++ b/commands/Music/remove.js @@ -31,6 +31,7 @@ class Remove extends Command { const tracks = queue.tracks.toArray(); const song = tracks[num]; queue.removeTrack(num); + return msg.channel.send(`\`${song.title}\` has been removed from the queue.`); } } diff --git a/commands/Music/stop.js b/commands/Music/stop.js index 59382aa3..592f0199 100755 --- a/commands/Music/stop.js +++ b/commands/Music/stop.js @@ -5,7 +5,7 @@ class Stop extends Command { constructor(client) { super(client, { name: 'stop', - description: 'Stops the music', + description: 'Stop playing amd clear the queue', category: 'Music', usage: 'stop', guildOnly: true, diff --git a/commands/Music/volume.js b/commands/Music/volume.js index 6e15a6fd..1b456d8c 100755 --- a/commands/Music/volume.js +++ b/commands/Music/volume.js @@ -7,7 +7,7 @@ class Volume extends Command { name: 'volume', description: 'Change the volume of the music', category: 'Music', - usage: 'volume', + usage: 'volume <1-100>', aliases: ['vol', 'v'], guildOnly: true, }); diff --git a/package.json b/package.json index c7742bbc..5510aa94 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mythical", - "version": "5.0.0", + "version": "5.1.0", "description": "Mythical Bot", "owner": "themondon", "main": "index.js", diff --git a/slash_commands/Music/music.js b/slash_commands/Music/music.js new file mode 100644 index 00000000..2b43d3d3 --- /dev/null +++ b/slash_commands/Music/music.js @@ -0,0 +1,415 @@ +const { EmbedBuilder, SlashCommandBuilder } = require('discord.js'); +const { lyricsExtractor } = require('@discord-player/extractor'); +const { useHistory, useQueue } = require('discord-player'); +const { stripIndents } = require('common-tags'); +const lyricsFinder = lyricsExtractor(); + +exports.conf = { + permLevel: 'User', + guildOnly: true, +}; + +exports.commandData = new SlashCommandBuilder() + .setName('music') + .setDescription('Control the music') + .addSubcommand((subcommand) => subcommand.setName('back').setDescription('Go back to the last song.')) + .addSubcommand((subcommand) => subcommand.setName('clear-queue').setDescription('Clears all songs from the queue')) + .addSubcommand((subcommand) => + subcommand + .setName('lyrics') + .setDescription('Get the lyrics of the current song, or another song') + .addStringOption((option) => option.setName('song').setDescription('The song to get lyrics to')), + ) + .addSubcommand((subcommand) => subcommand.setName('now-playing').setDescription('Shows what is currently playing')) + .addSubcommand((subcommand) => subcommand.setName('pause').setDescription('Pause the music')) + .addSubcommand((subcommand) => + subcommand + .setName('play') + .setDescription('Play something amazing') + .addStringOption((option) => option.setName('song').setDescription('The song or link to play').setRequired(true)), + ) + .addSubcommand((subcommand) => + subcommand + .setName('queue') + .setDescription('See what songs are in the queue') + .addIntegerOption((option) => option.setName('page').setDescription('The page of the queue to show')), + ) + .addSubcommand((subcommand) => + subcommand + .setName('remove') + .setDescription('Remove a track from the queue') + .addIntegerOption((option) => + option.setName('track').setDescription('The track number to remove').setRequired(true), + ), + ) + .addSubcommand((subcommand) => + subcommand + .setName('repeat') + .setDescription('Repeats the current track, queue, or enables autoplay.') + .addStringOption((option) => + option + .setName('type') + .setDescription('The type of autoplay') + .setRequired(true) + .addChoices( + { name: 'Off', value: 'off' }, + { name: 'Track', value: 'track' }, + { name: 'Queue', value: 'queue' }, + { name: 'Autoplay', value: 'autoplay' }, + ), + ), + ) + .addSubcommand((subcommand) => subcommand.setName('resume').setDescription('Resume the music')) + .addSubcommand((subcommand) => subcommand.setName('shuffle').setDescription('Shuffle the queue')) + .addSubcommand((subcommand) => subcommand.setName('skip').setDescription('Skip the current song')) + .addSubcommand((subcommand) => subcommand.setName('stop').setDescription('Stop playing and clear the queue')) + .addSubcommand((subcommand) => + subcommand + .setName('volume') + .setDescription('Change the music volume') + .addIntegerOption((option) => + option + .setName('level') + .setDescription('The level of the volume') + .setMinValue(1) + .setMaxValue(100) + .setRequired(true), + ), + ); + +exports.run = async (interaction) => { + await interaction.deferReply(); + const queue = useQueue(interaction.guild.id); + const subcommand = interaction.options.getSubcommand(); + + switch (subcommand) { + case 'back': { + const history = useHistory(interaction.guild.id); + const queue = useQueue(interaction.guild.id); + + if (!interaction.member.voice.channel) + return interaction.editReply('You must be in a voice channel to skip music.'); + if ( + interaction.guild.members.me.voice.channel && + interaction.member.voice.channel.id !== interaction.guild.members.me.voice.channel.id + ) + return interaction.editReply('You must be in the same voice channel as the bot.'); + if (!queue.node.isPlaying()) return interaction.editReply('There is nothing playing.'); + + await history.previous(); + const song = queue.currentTrack; + + const em = new EmbedBuilder() + .setColor(interaction.settings.embedSuccessColor) + .setAuthor({ name: interaction.member.displayName, iconURL: interaction.user.displayAvatarURL() }) + .addFields([{ name: 'Now Playing', value: song.title }]); + + return interaction.editReply({ embeds: [em] }); + } + + case 'clear-queue': { + const queue = interaction.client.player.nodes.get(interaction.guild.id); + + if (!interaction.member.voice.channel) + return interaction.editReply('You must be in a voice channel to clear the queue.'); + if ( + interaction.guild.members.me.voice.channel && + interaction.member.voice.channel.id !== interaction.guild.members.me.voice.channel.id + ) + return interaction.editReply('You must be in the same voice channel as the bot.'); + if (!queue) return interaction.editReply('There is nothing in the queue.'); + + queue.delete(false); + + const em = new EmbedBuilder().setDescription(':recycle: The music queue has been cleared!'); + return interaction.editReply({ embeds: [em] }); + } + + case 'lyrics': { + let song = interaction.options.get('song')?.value; + + if (!song) { + if (!interaction.guild) + return interaction.client.util.errorEmbed(interaction, "I can't get the lyrics of nothing."); + const playing = queue?.currentTrack; + song = `${playing?.author} ${playing?.title}`; + if (!playing || song === ' ') + return interaction.client.util.errorEmbed( + interaction, + 'Nothing is playing, please try again with a song name.', + ); + } + + const lyrics = await lyricsFinder.search(song).catch(() => null); + if (!lyrics) return interaction.editReply(`No lyrics found for: ${song}`); + const trimmedLyrics = lyrics.lyrics.substring(0, 3097); + + const em = new EmbedBuilder() + .setColor(interaction.settings.embedColor) + .setAuthor({ name: lyrics.artist.name, iconURL: lyrics.artist.image, url: lyrics.artist.url }) + .setTitle(lyrics.title) + .setURL(lyrics.url) + .setThumbnail(lyrics.thumbnail) + .setDescription(trimmedLyrics.length === 3090 ? `${trimmedLyrics}...` : trimmedLyrics); + return interaction.editReply({ embeds: [em] }); + } + + case 'now-playing': { + const song = queue?.currentTrack; + + if (!song) return interaction.editReply('There is nothing playing.'); + + const em = new EmbedBuilder() + .setDescription( + stripIndents` + Currently ${queue.node.isPlaying() ? 'Playing' : 'Paused'} ♪: [${song.title}](${song.url}) + + ${queue.node.createProgressBar({ timecodes: true })} + + Requested By: ${song.requestedBy} + `, + ) + .setColor(interaction.settings.embedColor) + .setThumbnail(song.thumbnail) + .setAuthor({ name: interaction.member.displayName, iconURL: interaction.user.displayAvatarURL() }); + return interaction.editReply({ embeds: [em] }); + } + + case 'pause': { + if (!interaction.member.voice.channel) + return interaction.editReply('You must be in a voice channel to pause music.'); + if ( + interaction.guild.members.me.voice.channel && + interaction.member.voice.channel.id !== interaction.guild.members.me.voice.channel.id + ) + return interaction.editReply('You must be in the same voice channel as the bot.'); + + queue.node.setPaused(!queue.node.isPaused()); + return interaction.editReply(`Music has been ${queue.node.isPaused() ? 'paused' : 'resumed'}`); + } + + case 'play': { + if (!interaction.member.voice.channel) + return interaction.editReply('You must be in a voice channel to play music.'); + if ( + interaction.guild.members.me.voice.channel && + interaction.member.voice.channel.id !== interaction.guild.members.me.voice.channel.id + ) + return interaction.editReply('You have to be in the same voice channel as the bot to play music'); + + const query = interaction.options.get('song').value; + + try { + const searchResult = await interaction.client.player.search(query, { requestedBy: interaction.user }); + + if (!searchResult) return interaction.editReply('I could not find that song.'); + if (!searchResult.hasTracks()) { + // If player didn't find any songs for this query + return interaction.editReply(`We couldn't find any tracks for ${query}!`); + } + + await interaction.client.player.play(interaction.member.voice.channel, searchResult, { + nodeOptions: { + metadata: interaction, + selfDead: true, + leaveOnStop: true, + leaveOnEnd: false, + leaveOnEmpty: false, + }, + }); + + const interactionMessage = await interaction.editReply('Music Started'); + return interactionMessage.delete().catch(() => {}); + } catch (e) { + return interaction.editReply(`Something went wrong: ${e}`); + } + } + + case 'queue': { + let page = interaction.options.get('page')?.value; + page = parseInt(page, 10); + + if (!queue || queue.tracks.size < 1) return interaction.editReply('There are no more songs in the queue.'); + if (!page) page = 1; + if (isNaN(page)) return interaction.editReply('Please input a valid number.'); + + let realPage = page; + let maxPages = page; + let q = queue.tracks.map((track, i) => { + return `${i + 1}. ${track.title} : ${track.author}`; + }); + let temp = q.slice(Math.floor((page - 1) * 25), Math.ceil(page * 25)); + + if (temp.length > 0) { + realPage = page; + maxPages = Math.ceil((q.length + 1) / 25); + q = temp; + } else { + for (let i = 1; i <= page; i++) { + temp = q.slice(Math.floor((i - 1) * 25), Math.ceil(i * 25)); + if (temp.length < 1) { + realPage = i - 1; + maxPages = Math.ceil(q.length / 25); + q = q.slice(Math.floor((i - 1 - 1) * 25), Math.ceil((i - 1) * 25)); + break; + } + } + } + + const authorName = interaction.user.discriminator === '0' ? interaction.user.username : interaction.user.tag; + const embed = new EmbedBuilder() + .setColor(interaction.settings.embedColor) + .setTitle(`${interaction.guild.name}'s Queue`) + .setAuthor({ name: authorName, iconURL: interaction.user.displayAvatarURL() }) + .setDescription(q.join('\n')) + .setFooter({ text: `Page ${realPage} / ${maxPages}` }) + .setTimestamp(); + return interaction.editReply({ embeds: [embed] }); + } + + case 'remove': { + const track = interaction.options.get('track').value; + + if (!interaction.member.voice.channel) + return interaction.editReply('You must be in a voice channel to modify the queue.'); + if ( + interaction.guild.members.me.voice.channel && + interaction.member.voice.channel.id !== interaction.guild.members.me.voice.channel.id + ) + return interaction.editReply('You must be in the same voice channel as the bot.'); + + if (!queue) return interaction.editReply('The queue is empty.'); + if (!queue.isPlaying()) return interaction.editReply('There is nothing playing.'); + + const num = parseInt(track, 10) - 1; + if (isNaN(num)) return interaction.editReply('Please supply a valid number.'); + + const ql = queue.tracks.size; + if (num > ql) return interaction.editReply("You can't remove something that is not in the queue."); + + const tracks = queue.tracks.toArray(); + const song = tracks[num]; + queue.removeTrack(num); + + return interaction.editReply(`\`${song.title}\` has been removed from the queue.`); + } + + case 'repeat': { + const type = interaction.options.get('type').value; + + if (!interaction.member.voice.channel) + return interaction.editReply('You must be in a voice channel to loop music.'); + if ( + interaction.guild.members.me.voice.channel && + interaction.member.voice.channel.id !== interaction.guild.members.me.voice.channel.id + ) + return interaction.editReply('You must be in the same voice channel as the bot.'); + if (!queue) return interaction.editReply('There is nothing in the queue.'); + + switch (type) { + case 'off': { + queue.setRepeatMode(0); + return interaction.editReply('Stopped repeat mode.'); + } + + case 'track': { + queue.setRepeatMode(1); + const song = queue.currentTrack; + return interaction.editReply(`Now Repeating: ${song.title}`); + } + + case 'queue': { + queue.setRepeatMode(2); + return interaction.editReply('Now repeating whole queue.'); + } + + case 'autoplay': { + queue.setRepeatMode(3); + return interaction.editReply('Turned on autoplay.'); + } + } + break; + } + + case 'resume': { + if (!interaction.member.voice.channel) + return interaction.editReply('You must be in a voice channel to resume music.'); + if ( + interaction.guild.members.me.voice.channel && + interaction.member.voice.channel.id !== interaction.guild.members.me.voice.channel.id + ) + return interaction.editReply('You must be in the same voice channel as the bot.'); + + queue.node.setPaused(!queue.node.isPaused()); + return interaction.editReply(`Music has been ${queue.node.isPaused() ? 'paused' : 'resumed'}`); + } + + case 'shuffle': { + if (!interaction.member.voice.channel) + return interaction.editReply('You must be in a voice channel to shuffle the queue.'); + if ( + interaction.guild.members.me.voice.channel && + interaction.member.voice.channel.id !== interaction.guild.members.me.voice.channel.id + ) + return interaction.editReply('You must be in the same voice channel as the bot.'); + + if (!queue) return interaction.editReply('There is nothing in the queue.'); + + queue.tracks.shuffle(); + return interaction.editReply('The queue has been shuffled.'); + } + + case 'skip': { + if (!interaction.member.voice.channel) + return interaction.editReply('You must be in a voice channel to skip music.'); + if ( + interaction.guild.members.me.voice.channel && + interaction.member.voice.channel.id !== interaction.guild.members.me.voice.channel.id + ) + return interaction.editReply('You must be in the same voice channel as the bot.'); + if (!queue) return interaction.editReply('There is nothing playing.'); + + const song = queue.currentTrack; + queue.node.skip(); + + const em = new EmbedBuilder() + .setColor(interaction.settings.embedSuccessColor) + .setAuthor({ name: interaction.member.displayName, iconURL: interaction.user.displayAvatarURL() }); + if (song) em.addFields([{ name: 'Skipped Song', value: song.title, inline: false }]); + + return interaction.editReply({ embeds: [em] }); + } + + case 'stop': { + if (!interaction.member.voice.channel) + return interaction.editReply('You must be in a voice channel to stop music.'); + if ( + interaction.guild.members.me.voice.channel && + interaction.member.voice.channel.id !== interaction.guild.members.me.voice.channel.id + ) + return interaction.editReply('You must be in the same voice channel as the bot.'); + + if (!queue) return interaction.editReply('There was nothing playing.'); + + queue.delete(); + + return interaction.editReply('All music has been stopped.'); + } + + case 'volume': { + if (!interaction.member.voice.channel) + return interaction.editReply('You must be in a voice channel to change the volume.'); + if ( + interaction.guild.members.me.voice.channel && + interaction.member.voice.channel.id !== interaction.guild.members.me.voice.channel.id + ) + return interaction.editReply('You must be in the same voice channel as the bot.'); + if (!queue.node.isPlaying()) return interaction.editReply('There is nothing playing.'); + + const volume = interaction.options.get('level').value; + queue.node.setVolume(volume); + + return interaction.editReply(`The volume has been set to: ${volume}`); + } + } +}; diff --git a/slash_commands/Search/search.js b/slash_commands/Search/search.js index 717d734d..5f643f72 100644 --- a/slash_commands/Search/search.js +++ b/slash_commands/Search/search.js @@ -43,7 +43,7 @@ exports.commandData = new SlashCommandBuilder() .addSubcommand((subcommand) => subcommand .setName('github') - .setDescription('View information about a steam game or application') + .setDescription('View information about a user or a repository') .addStringOption((option) => option.setName('user').setDescription('Github username').setRequired(true)) .addStringOption((option) => option.setName('repo').setDescription('Github repo').setRequired(false)), ) @@ -92,6 +92,7 @@ exports.run = async (interaction) => { return interaction.editReply({ embeds: [embed] }); } } + case 'movie': { const query = interaction.options.get('movie').value; @@ -137,6 +138,7 @@ exports.run = async (interaction) => { return interaction.editReply({ embeds: [embed] }); } + case 'tv-show': { const query = interaction.options.get('tv-show').value; const search = await fetch.get('http://api.themoviedb.org/3/search/tv').query({ @@ -188,6 +190,7 @@ exports.run = async (interaction) => { return interaction.editReply({ embeds: [embed] }); } + case 'steam': { const game = interaction.options.get('game').value; const query = await interaction.client.util.clean(interaction.client, game); @@ -251,6 +254,7 @@ exports.run = async (interaction) => { return interaction.editReply({ embeds: [embed] }); } + case 'github': { const user = interaction.options.get('user').value; const repo = interaction.options.get('repo')?.value; @@ -331,6 +335,7 @@ exports.run = async (interaction) => { return interaction.editReply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); } } + case 'npm': { const packageName = interaction.options.get('package').value;