From 59c2c230fbc63b7bc562c1f601858602459ed50e Mon Sep 17 00:00:00 2001 From: TheEssem Date: Mon, 6 Jul 2020 15:19:30 -0500 Subject: [PATCH] Added initial music commands and category, moved lavalink node config to separate file --- .env.example | 2 - application.yml | 1 + commands/addtweet.js | 2 +- commands/eval.js | 2 +- commands/exec.js | 2 +- commands/help.js | 7 +- commands/nowplaying.js | 10 ++ commands/pause.js | 10 ++ commands/play.js | 10 +- commands/queue.js | 10 ++ commands/reload.js | 2 +- commands/restart.js | 4 +- commands/skip.js | 9 ++ commands/spam.js | 2 +- commands/stop.js | 10 ++ commands/tweet.js | 2 +- events/ready.js | 4 +- events/voiceChannelLeave.js | 41 +++++++ events/voiceChannelSwitch.js | 5 + lavanodes.json | 3 + utils/awaitrejoin.js | 40 ++++++ utils/help.js | 8 +- utils/soundplayer.js | 232 ++++++++++++++++++++++++++++++----- 23 files changed, 365 insertions(+), 53 deletions(-) create mode 100644 commands/nowplaying.js create mode 100644 commands/pause.js create mode 100644 commands/queue.js create mode 100644 commands/skip.js create mode 100644 commands/stop.js create mode 100644 events/voiceChannelLeave.js create mode 100644 events/voiceChannelSwitch.js create mode 100644 lavanodes.json create mode 100644 utils/awaitrejoin.js diff --git a/.env.example b/.env.example index c58848a..59891e2 100644 --- a/.env.example +++ b/.env.example @@ -11,8 +11,6 @@ TOKEN= MONGO=mongodb://localhost:27017/esmBot # Put snowflake ID of bot owner here OWNER= -# Put Lavalink password here -LAVAPASS=youshallnotpass ########### # Optional diff --git a/application.yml b/application.yml index fd9cadf..94924b7 100644 --- a/application.yml +++ b/application.yml @@ -15,6 +15,7 @@ lavalink: local: true bufferDurationMs: 400 youtubePlaylistLoadLimit: 6 # Number of pages at 100 each + playerUpdateInterval: 1 youtubeSearchEnabled: true soundcloudSearchEnabled: true gc-warnings: true diff --git a/commands/addtweet.js b/commands/addtweet.js index 1b9afe0..40e333e 100644 --- a/commands/addtweet.js +++ b/commands/addtweet.js @@ -11,7 +11,7 @@ exports.run = async (message, args) => { }; exports.aliases = ["add"]; -exports.category = 7; +exports.category = 8; exports.help = "Adds a tweet to the database"; exports.requires = "twitter"; exports.params = "[category] [message]"; \ No newline at end of file diff --git a/commands/eval.js b/commands/eval.js index 1d37335..9a52398 100644 --- a/commands/eval.js +++ b/commands/eval.js @@ -22,6 +22,6 @@ exports.run = async (message, args) => { }; exports.aliases = ["run"]; -exports.category = 7; +exports.category = 8; exports.help = "Executes JavaScript code"; exports.params = "[code]"; \ No newline at end of file diff --git a/commands/exec.js b/commands/exec.js index 7139820..c041bba 100644 --- a/commands/exec.js +++ b/commands/exec.js @@ -25,6 +25,6 @@ exports.run = async (message, args) => { }; exports.aliases = ["runcmd"]; -exports.category = 7; +exports.category = 8; exports.help = "Executes a terminal command"; exports.params = "[command]"; \ No newline at end of file diff --git a/commands/help.js b/commands/help.js index b3728ae..df45213 100644 --- a/commands/help.js +++ b/commands/help.js @@ -37,8 +37,9 @@ exports.run = async (message, args) => { moderation: [], tags: ["**Every command in this category is a subcommand of the tag command.**\n"], fun: [], - images: ["**These commands support the PNG, JPEG, WEBP, and GIF formats. (GIF support is experimental)**\n"], - soundboard: [] + images: ["**These commands support the PNG, JPEG, WEBP (static), and GIF (animated or static) formats.**\n"], + soundboard: [], + music: [] }; for (const [command] of commands) { const category = collections.info.get(command).category; @@ -59,6 +60,8 @@ exports.run = async (message, args) => { categories.images.push(`**${command}**${params ? ` ${params}` : ""} - ${description}`); } else if (category === 6) { categories.soundboard.push(`**${command}**${params ? ` ${params}` : ""} - ${description}`); + } else if (category === 7) { + categories.music.push(`**${command}**${params ? ` ${params}` : ""} - ${description}`); } } const pages = []; diff --git a/commands/nowplaying.js b/commands/nowplaying.js new file mode 100644 index 0000000..eb984d6 --- /dev/null +++ b/commands/nowplaying.js @@ -0,0 +1,10 @@ +const soundPlayer = require("../utils/soundplayer.js"); + +exports.run = async (message) => { + soundPlayer.playing(message); +}; + +exports.aliases = ["playing", "np"]; +exports.category = 7; +exports.help = "Shows the currently playing song"; +exports.requires = "sound"; \ No newline at end of file diff --git a/commands/pause.js b/commands/pause.js new file mode 100644 index 0000000..91524bb --- /dev/null +++ b/commands/pause.js @@ -0,0 +1,10 @@ +const soundPlayer = require("../utils/soundplayer.js"); + +exports.run = async (message) => { + soundPlayer.pause(message); +}; + +exports.aliases = ["resume"]; +exports.category = 7; +exports.help = "Pauses/resumes the current song"; +exports.requires = "sound"; \ No newline at end of file diff --git a/commands/play.js b/commands/play.js index 9694361..1830bba 100644 --- a/commands/play.js +++ b/commands/play.js @@ -1,10 +1,12 @@ const soundPlayer = require("../utils/soundplayer.js"); exports.run = async (message, args) => { - if (message.author.id !== process.env.OWNER) return `${message.author.mention}, this command is for testing and is restricted to owners.`; - return soundPlayer.play(encodeURIComponent(args.join(" ")), message); + if (!args[0]) return `${message.author.mention}, you need to provide what you want to play!`; + soundPlayer.play(encodeURIComponent(args.join(" ").trim()), message, true); }; +exports.aliases = ["p"]; exports.category = 7; -exports.help = "Plays an audio file"; -exports.requires = "sound"; \ No newline at end of file +exports.help = "Plays a song or adds it to the queue"; +exports.requires = "sound"; +exports.params = "[url]"; \ No newline at end of file diff --git a/commands/queue.js b/commands/queue.js new file mode 100644 index 0000000..4ec9199 --- /dev/null +++ b/commands/queue.js @@ -0,0 +1,10 @@ +const soundPlayer = require("../utils/soundplayer.js"); + +exports.run = async (message) => { + soundPlayer.queue(message); +}; + +exports.aliases = ["q"]; +exports.category = 7; +exports.help = "Shows the current queue"; +exports.requires = "sound"; \ No newline at end of file diff --git a/commands/reload.js b/commands/reload.js index b8aece8..e197d42 100644 --- a/commands/reload.js +++ b/commands/reload.js @@ -12,6 +12,6 @@ exports.run = async (message, args) => { } }; -exports.category = 7; +exports.category = 8; exports.help = "Reloads a command"; exports.params = "[command]"; \ No newline at end of file diff --git a/commands/restart.js b/commands/restart.js index e07bd3c..d47b683 100644 --- a/commands/restart.js +++ b/commands/restart.js @@ -10,6 +10,6 @@ exports.run = async (message) => { process.exit(1); }; -exports.aliases = ["reboot", "stop"]; -exports.category = 7; +exports.aliases = ["reboot"]; +exports.category = 8; exports.help = "Restarts me"; \ No newline at end of file diff --git a/commands/skip.js b/commands/skip.js new file mode 100644 index 0000000..d265850 --- /dev/null +++ b/commands/skip.js @@ -0,0 +1,9 @@ +const soundPlayer = require("../utils/soundplayer.js"); + +exports.run = async (message) => { + soundPlayer.skip(message); +}; + +exports.category = 7; +exports.help = "Skips the current song"; +exports.requires = "sound"; \ No newline at end of file diff --git a/commands/spam.js b/commands/spam.js index b29bc12..1e71cbe 100644 --- a/commands/spam.js +++ b/commands/spam.js @@ -4,6 +4,6 @@ exports.run = async (message, args) => { return args.join(" ").repeat(500).substring(0, 500); }; -exports.category = 8; +exports.category = 9; exports.help = "placeholder"; exports.params = "[text]"; \ No newline at end of file diff --git a/commands/stop.js b/commands/stop.js new file mode 100644 index 0000000..8a96271 --- /dev/null +++ b/commands/stop.js @@ -0,0 +1,10 @@ +const soundPlayer = require("../utils/soundplayer.js"); + +exports.run = async (message) => { + soundPlayer.stop(message); +}; + +exports.aliases = ["disconnect"]; +exports.category = 7; +exports.help = "Stops the music"; +exports.requires = "sound"; \ No newline at end of file diff --git a/commands/tweet.js b/commands/tweet.js index 69be94a..1faf2c7 100644 --- a/commands/tweet.js +++ b/commands/tweet.js @@ -9,7 +9,7 @@ exports.run = async (message, args) => { // with status code ${info.resp.statusCode} ${info.resp.statusMessage}. }; -exports.category = 7; +exports.category = 8; exports.help = "Tweets a message"; exports.requires = "twitter"; exports.params = "[message]"; \ No newline at end of file diff --git a/events/ready.js b/events/ready.js index 5256b84..6b0b5b1 100644 --- a/events/ready.js +++ b/events/ready.js @@ -120,9 +120,7 @@ module.exports = async () => { } // generate docs - if (helpGenerator) { - await helpGenerator(process.env.OUTPUT); - } + if (helpGenerator) await helpGenerator(process.env.OUTPUT); // set activity (a.k.a. the gamer code) (async function activityChanger() { diff --git a/events/voiceChannelLeave.js b/events/voiceChannelLeave.js new file mode 100644 index 0000000..c5de0b4 --- /dev/null +++ b/events/voiceChannelLeave.js @@ -0,0 +1,41 @@ +const soundPlayer = require("../utils/soundplayer.js"); +const client = require("../utils/client.js"); +const AwaitRejoin = require("../utils/awaitrejoin.js"); + +module.exports = async (member, oldChannel) => { + const connection = soundPlayer.players.get(oldChannel.guild.id); + if (connection && oldChannel.id === connection.voiceChannel.id) { + if (oldChannel.voiceMembers.filter((i) => i.id !== client.user.id).length === 0) { + const waitMessage = await client.createMessage(connection.originalChannel.id, "🔊 Waiting 10 seconds for someone to return..."); + const awaitRejoin = new AwaitRejoin(oldChannel, true); + awaitRejoin.on("end", (rejoined, member) => { + if (rejoined) { + soundPlayer.players.set(connection.voiceChannel.guild.id, { player: connection.player, type: connection.type, host: member.id, voiceChannel: connection.voiceChannel, originalChannel: connection.originalChannel }); + waitMessage.edit(`🔊 ${member.mention} is the new voice channel host.`); + } else { + waitMessage.delete(); + connection.player.stop(connection.originalChannel.guild.id); + } + }); + } else if (member.id === connection.host) { + const waitMessage = await client.createMessage(connection.originalChannel.id, "🔊 Waiting 10 seconds for the host to return..."); + const awaitRejoin = new AwaitRejoin(oldChannel, false, member.id); + awaitRejoin.on("end", (rejoined) => { + if (rejoined) { + waitMessage.delete(); + } else { + const members = oldChannel.voiceMembers.filter((i) => i.id !== client.user.id); + if (members.length === 0) { + waitMessage.delete(); + connection.player.stop(connection.originalChannel.guild.id); + } else { + const randomMember = members.random(); + soundPlayer.players.set(connection.voiceChannel.guild.id, { player: connection.player, type: connection.type, host: randomMember.id, voiceChannel: connection.voiceChannel, originalChannel: connection.originalChannel }); + waitMessage.edit(`🔊 ${randomMember.mention} is the new voice channel host.`); + } + + } + }); + } + } +}; \ No newline at end of file diff --git a/events/voiceChannelSwitch.js b/events/voiceChannelSwitch.js new file mode 100644 index 0000000..6dd9561 --- /dev/null +++ b/events/voiceChannelSwitch.js @@ -0,0 +1,5 @@ +const leaveHandler = require("./voiceChannelLeave.js"); + +module.exports = async (member, newChannel, oldChannel) => { + await leaveHandler(member, oldChannel); +}; \ No newline at end of file diff --git a/lavanodes.json b/lavanodes.json new file mode 100644 index 0000000..9549f9c --- /dev/null +++ b/lavanodes.json @@ -0,0 +1,3 @@ +[ + { "id": "1", "host": "localhost", "port": 2333, "password": "youshallnotpass" } +] \ No newline at end of file diff --git a/utils/awaitrejoin.js b/utils/awaitrejoin.js new file mode 100644 index 0000000..ab8a2dd --- /dev/null +++ b/utils/awaitrejoin.js @@ -0,0 +1,40 @@ +// this is a method to wait for someone to rejoin a voice channel +const { EventEmitter } = require("events"); + +class AwaitRejoin extends EventEmitter { + constructor(channel, anyone, memberID) { + super(); + this.member = memberID; + this.anyone = anyone; + this.channel = channel; + this.rejoined = false; + this.ended = false; + this.bot = channel.guild ? channel.guild.shard.client : channel._client; + this.listener = (member, newChannel) => this.verify(member, newChannel); + this.bot.on("voiceChannelJoin", this.listener); + this.bot.on("voiceChannelSwitch", this.listener); + setTimeout(() => this.stop(), 10000); + } + + verify(member, channel) { + if (this.channel.id === channel.id) { + if (this.member === member.id || this.anyone) { + this.rejoined = true; + this.stop(member); + return true; + } + } else { + return false; + } + } + + stop(member) { + if (this.ended) return; + this.ended = true; + this.bot.removeListener("voiceChannelJoin", this.listener); + this.bot.removeListener("voiceChannelSwitch", this.listener); + this.emit("end", this.rejoined, member); + } +} + +module.exports = AwaitRejoin; \ No newline at end of file diff --git a/utils/help.js b/utils/help.js index 59cf567..28ecdf9 100644 --- a/utils/help.js +++ b/utils/help.js @@ -18,6 +18,7 @@ Default prefix is \`&\`. + [**Fun**](#👌-fun) + [**Image Editing**](#đŸ–ŧī¸-image-editing) + [**Soundboard**](#🔊-soundboard) ++ [**Music**](#🎤-music) `; const commands = collections.commands; const categories = { @@ -26,7 +27,8 @@ Default prefix is \`&\`. tags: ["## 🏷ī¸ Tags"], fun: ["## 👌 Fun"], images: ["## đŸ–ŧī¸ Image Editing", "> These commands support the PNG, JPEG, WEBP, and GIF formats. (GIF support is currently experimental)"], - soundboard: ["## 🔊 Soundboard"] + soundboard: ["## 🔊 Soundboard"], + music: ["## 🎤 Music"] }; for (const [command] of commands) { const category = collections.info.get(command).category; @@ -47,9 +49,11 @@ Default prefix is \`&\`. categories.images.push(`+ **${command}**${params ? ` ${params}` : ""} - ${description}`); } else if (category === 6) { categories.soundboard.push(`+ **${command}**${params ? ` ${params}` : ""} - ${description}`); + } else if (category === 7) { + categories.music.push(`+ **${command}**${params ? ` ${params}` : ""} - ${description}`); } } - fs.writeFile(output, `${template}\n${categories.general.join("\n")}\n\n${categories.moderation.join("\n")}\n\n${categories.tags.join("\n")}\n\n${categories.fun.join("\n")}\n\n${categories.images.join("\n")}\n\n${categories.soundboard.join("\n")}`, () => { + fs.writeFile(output, `${template}\n${categories.general.join("\n")}\n\n${categories.moderation.join("\n")}\n\n${categories.tags.join("\n")}\n\n${categories.fun.join("\n")}\n\n${categories.images.join("\n")}\n\n${categories.soundboard.join("\n")}\n\n${categories.music.join("\n")}`, () => { logger.log("The help docs have been generated."); }); }; \ No newline at end of file diff --git a/utils/soundplayer.js b/utils/soundplayer.js index a98ff89..b090b05 100644 --- a/utils/soundplayer.js +++ b/utils/soundplayer.js @@ -1,13 +1,19 @@ const client = require("./client.js"); const logger = require("./logger.js"); +const paginator = require("./pagination/pagination.js"); const fetch = require("node-fetch"); +const moment = require("moment"); +require("moment-duration-format"); const { Manager } = require("@lavacord/eris"); -const nodes = [ - { id: "1", host: "localhost", port: 2333, password: process.env.LAVAPASS } -]; +const nodes = require("../lavanodes.json"); -let manager; +exports.players = new Map(); + +const queues = new Map(); +const skipVotes = new Map(); + +exports.manager; exports.status = false; @@ -27,41 +33,203 @@ exports.checkStatus = async () => { }; exports.connect = async () => { - manager = new Manager(client, nodes, { + this.manager = new Manager(client, nodes, { user: client.user.id }); - const { length } = await manager.connect(); + const { length } = await this.manager.connect(); logger.log(`Successfully connected to ${length} Lavalink node(s).`); - manager.on("error", (error, node) => { + this.manager.on("error", (error, node) => { logger.error(`An error occurred on Lavalink node ${node}: ${error}`); }); }; -exports.play = async (sound, message) => { - if (message.member.voiceState.channelID) { - if (!message.channel.guild.members.get(client.user.id).permission.has("voiceConnect") || !message.channel.permissionsOf(client.user.id).has("voiceConnect")) return client.createMessage(message.channel.id, `${message.author.mention}, I can't join this voice channel!`); - const voiceChannel = message.channel.guild.channels.get(message.member.voiceState.channelID); - if (!voiceChannel.permissionsOf(client.user.id).has("voiceConnect")) return client.createMessage(message.channel.id, `${message.author.mention}, I don't have permission to join this voice channel!`); - const node = manager.idealNodes[0]; - const { tracks } = await fetch(`http://${node.host}:${node.port}/loadtracks?identifier=${sound}`, { headers: { Authorization: node.password } }).then(res => res.json()); - const connection = await manager.join({ - guild: voiceChannel.guild.id, - channel: voiceChannel.id, - node: node.id - }); - const playingMessage = await client.createMessage(message.channel.id, "🔊 Playing sound..."); - await connection.play(tracks[0].track); - connection.on("error", (error) => { - manager.leave(voiceChannel.guild.id); - playingMessage.delete(); - logger.error(error); - }); - connection.once("end", (data) => { - if (data.reason === "REPLACED") return; - manager.leave(voiceChannel.guild.id); - playingMessage.delete(); - }); +exports.play = async (sound, message, music = false) => { + if (!message.member.voiceState.channelID) return client.createMessage(message.channel.id, `${message.author.mention}, you need to be in a voice channel first!`); + if (!message.channel.guild.members.get(client.user.id).permission.has("voiceConnect") || !message.channel.permissionsOf(client.user.id).has("voiceConnect")) return client.createMessage(message.channel.id, `${message.author.mention}, I can't join this voice channel!`); + const voiceChannel = message.channel.guild.channels.get(message.member.voiceState.channelID); + if (!voiceChannel.permissionsOf(client.user.id).has("voiceConnect")) return client.createMessage(message.channel.id, `${message.author.mention}, I don't have permission to join this voice channel!`); + if (!music && this.manager.voiceStates.has(message.channel.guild.id) && this.players.get(message.channel.guild.id).type === "music") return client.createMessage(message.channel.id, `${message.author.mention}, I can't play a sound effect while playing music!`); + const node = this.manager.idealNodes[0]; + const { tracks } = await fetch(`http://${node.host}:${node.port}/loadtracks?identifier=${sound}`, { headers: { Authorization: node.password } }).then(res => res.json()); + const connection = await this.manager.join({ + guild: voiceChannel.guild.id, + channel: voiceChannel.id, + node: node.id + }); + const oldQueue = queues.get(voiceChannel.guild.id); + console.log(tracks); + queues.set(voiceChannel.guild.id, oldQueue ? [...oldQueue, tracks[0].track] : [tracks[0].track]); + + if (oldQueue) { + client.createMessage(message.channel.id, `${message.author.mention}, your tune has been added to the queue!`); } else { - client.createMessage(message.channel.id, `${message.author.mention}, you need to be in a voice channel first!`); + this.nextSong(message, connection, tracks[0].track, tracks[0].info, music, voiceChannel); } }; + +exports.nextSong = async (message, connection, track, info, music, voiceChannel) => { + const parts = Math.floor((0 / info.length) * 10); + const playingMessage = await client.createMessage(message.channel.id, !music ? "🔊 Playing sound..." : { + "embed": { + "color": 16711680, + "author": { + "name": "Now Playing", + "icon_url": client.user.avatarURL + }, + "fields": [{ + "name": "ℹī¸ Title:", + "value": info.title + }, + { + "name": "🎤 Artist:", + "value": info.author + }, + { + "name": "đŸ’Ŧ Channel:", + "value": voiceChannel.name + }, + { + "name": `${"â–Ŧ".repeat(parts)}🔘${"â–Ŧ".repeat(10 - parts)}`, + "value": `${moment.duration(0).format("m:ss", { trim: false })}/${info.isStream ? "∞" : moment.duration(info.length).format("m:ss", { trim: false })}` + }] + } + }); + await connection.play(track); + this.players.set(voiceChannel.guild.id, { player: connection, type: music ? "music" : "sound", host: message.author.id, voiceChannel: voiceChannel, originalChannel: message.channel }); + connection.on("error", (error) => { + playingMessage.delete(); + this.manager.leave(voiceChannel.guild.id); + this.players.delete(voiceChannel.guild.id); + queues.delete(voiceChannel.guild.id); + throw error; + }); + connection.once("end", async (data) => { + if (data.reason === "REPLACED") return; + const queue = queues.get(voiceChannel.guild.id); + const newQueue = queue.slice(1); + queues.set(voiceChannel.guild.id, newQueue); + await playingMessage.delete(); + if (newQueue.length === 0) { + this.manager.leave(voiceChannel.guild.id); + this.players.delete(voiceChannel.guild.id); + queues.delete(voiceChannel.guild.id); + await client.createMessage(message.channel.id, "🔊 The current voice channel session has ended."); + } else { + const track = await fetch(`http://${connection.node.host}:${connection.node.port}/decodetrack?track=${encodeURIComponent(newQueue[0])}`, { headers: { Authorization: connection.node.password } }).then(res => res.json()); + this.nextSong(message, connection, newQueue[0], track, music, voiceChannel); + } + }); +}; + +exports.stop = async (message) => { + if (!message.member.voiceState.channelID) return client.createMessage(message.channel.id, `${message.author.mention}, you need to be in a voice channel first!`); + if (!message.channel.guild.members.get(client.user.id).voiceState.channelID) return client.createMessage(message.channel.id, `${message.author.mention}, I'm not in a voice channel!`); + if (this.players.get(message.channel.guild.id).host !== message.author.id) return client.createMessage(message.channel.id, `${message.author.mention}, only the current voice session host can stop the music!`); + this.players.delete(message.channel.guild.id); + this.manager.leave(message.channel.guild.id); + await client.createMessage(message.channel.id, "🔊 The current voice channel session has ended."); +}; + +exports.skip = async (message) => { + if (!message.member.voiceState.channelID) return client.createMessage(message.channel.id, `${message.author.mention}, you need to be in a voice channel first!`); + if (!message.channel.guild.members.get(client.user.id).voiceState.channelID) return client.createMessage(message.channel.id, `${message.author.mention}, I'm not in a voice channel!`); + const player = this.players.get(message.channel.guild.id); + if (player.host !== message.author.id) { + const voteCount = skipVotes.has(message.guild.id) ? skipVotes.get(message.guild.id) : 0; + if (voteCount + 1 === 3) { + player.player.stop(message.channel.guild.id); + skipVotes.set(message.guild.id, 0); + } else { + await client.createMessage(message.channel.id, `🔊 Voted to skip song (${voteCount + 1}/3 people have voted).`); + skipVotes.set(message.guild.id, voteCount + 1); + } + } else { + player.player.stop(message.channel.guild.id); + } +}; + +exports.pause = async (message) => { + if (!message.member.voiceState.channelID) return client.createMessage(message.channel.id, `${message.author.mention}, you need to be in a voice channel first!`); + if (!message.channel.guild.members.get(client.user.id).voiceState.channelID) return client.createMessage(message.channel.id, `${message.author.mention}, I'm not in a voice channel!`); + if (this.players.get(message.channel.guild.id).host !== message.author.id) return client.createMessage(message.channel.id, `${message.author.mention}, only the current voice session host can pause/resume the music!`); + const player = this.players.get(message.channel.guild.id).player; + player.pause(!player.paused ? true : false); + await client.createMessage(message.channel.id, `🔊 The player has been ${!player.paused ? "paused" : "resumed"}.`); +}; + +exports.playing = async (message) => { + if (!message.member.voiceState.channelID) return client.createMessage(message.channel.id, `${message.author.mention}, you need to be in a voice channel first!`); + if (!message.channel.guild.members.get(client.user.id).voiceState.channelID) return client.createMessage(message.channel.id, `${message.author.mention}, I'm not in a voice channel!`); + const player = this.players.get(message.channel.guild.id).player; + if (!player) return client.createMessage(message.channel.id, `${message.author.mention}, I'm not playing anything!`); + const track = await fetch(`http://${player.node.host}:${player.node.port}/decodetrack?track=${encodeURIComponent(player.track)}`, { headers: { Authorization: player.node.password } }).then(res => res.json()); + const parts = Math.floor((player.state.position / track.length) * 10); + await client.createMessage(message.channel.id, { + "embed": { + "color": 16711680, + "author": { + "name": "Now Playing", + "icon_url": client.user.avatarURL + }, + "fields": [{ + "name": "ℹī¸ Title:", + "value": track.title + }, + { + "name": "🎤 Artist:", + "value": track.author + }, + { + "name": "đŸ’Ŧ Channel:", + "value": message.channel.guild.channels.get(message.member.voiceState.channelID).name + }, + { + "name": `${"â–Ŧ".repeat(parts)}🔘${"â–Ŧ".repeat(10 - parts)}`, + "value": `${moment.duration(player.state.position).format("m:ss", { trim: false })}/${track.isStream ? "∞" : moment.duration(track.length).format("m:ss", { trim: false })}` + }] + } + }); +}; + +exports.queue = async (message) => { + if (!message.member.voiceState.channelID) return client.createMessage(message.channel.id, `${message.author.mention}, you need to be in a voice channel first!`); + if (!message.channel.guild.members.get(client.user.id).voiceState.channelID) return client.createMessage(message.channel.id, `${message.author.mention}, I'm not in a voice channel!`); + if (!message.channel.guild.members.get(client.user.id).permission.has("addReactions") && !message.channel.permissionsOf(client.user.id).has("addReactions")) return `${message.author.mention}, I don't have the \`Add Reactions\` permission!`; + if (!message.channel.guild.members.get(client.user.id).permission.has("embedLinks") && !message.channel.permissionsOf(client.user.id).has("embedLinks")) return `${message.author.mention}, I don't have the \`Embed Links\` permission!`; + const queue = queues.get(message.channel.guild.id); + const player = this.players.get(message.channel.guild.id).player; + const tracks = await fetch(`http://${player.node.host}:${player.node.port}/decodetracks`, { method: "POST", body: JSON.stringify(queue), headers: { Authorization: player.node.password, "Content-Type": "application/json" } }).then(res => res.json()); + const trackList = []; + const firstTrack = tracks.shift(); + for (const [i, track] of tracks.entries()) { + trackList.push(`${i + 1}. ${track.info.author} - **${track.info.title}** (${track.info.isStream ? "∞" : moment.duration(track.info.length).format("m:ss", { trim: false })})`); + } + const pageSize = 5; + const embeds = []; + const groups = trackList.map((item, index) => { + return index % pageSize === 0 ? trackList.slice(index, index + pageSize) : null; + }).filter(Boolean); + for (const [i, value] of groups.entries()) { + embeds.push({ + "embed": { + "author": { + "name": "Queue", + "icon_url": client.user.avatarURL + }, + "color": 16711680, + "footer": { + "text": `Page ${i + 1} of ${groups.length}` + }, + "fields": [{ + "name": "đŸŽļ Now Playing", + "value": `${firstTrack.info.author} - **${firstTrack.info.title}** (${firstTrack.info.isStream ? "∞" : moment.duration(firstTrack.info.length).format("m:ss", { trim: false })})` + }, { + "name": "🗒ī¸ Queue", + "value": value.join("\n") + }] + } + }); + } + if (embeds.length === 0) return `${message.author.mention}, there's nothing in the queue!`; + return paginator(message, embeds); +}; \ No newline at end of file