UNPKG

la-music-core

Version:
776 lines (771 loc) 36.6 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const discord_js_1 = require("discord.js"); const ytdl = require("ytdl-core"); var YouTube = require("simple-youtube-api"); class musicClient { /** * Options for the music client * @typedef {object} ClientOptions * @property {boolean} [earProtections=true] Whether to protect ears from high volume of music. * @property {boolean} [loop=false] Whether to loop the queue by default. * @property {number} [songChooseTimeout=10] The default timeout for song choosing, in terms of seconds. * @property {number} [volume=30] The default client volume to be used. */ /** * @param {string} YouTubeApiKey The YouTube Data Api Key v3 to use. * @param {ClientOptions} [options] The music client options avalible to configure. */ constructor(YouTubeApiKey, options = { earProtections: true, loop: false, songChooseTimeout: 10, volume: 30 }) { if (typeof YouTubeApiKey !== "string") throw new Error("The YouTube Api Key provided is not a string."); this.google_api_key = YouTubeApiKey; this.youtube = new YouTube(this.google_api_key); this.queueList = new Map(); this.settings = {}; if (options.songChooseTimeout) this.settings.songChooseTimeout = options.songChooseTimeout * 1000; else this.settings.songChooseTimeout = 10000; if (options.volume) this.settings.volume = options.volume; else this.settings.volume = 30; if (options.earProtections !== true) { console.log("Caution : The volume limit cap has been removed.\nPlease be sure not to unintentionally input a volume higher than 100, or it may damage your device and/or ears."); this.settings.earProtections = options.earProtections; } else this.settings.earProtections = true; if (options.loop) this.settings.loop = options.loop; else this.settings.loop = false; } /** * Play the music requested in a voice channel with the command user. * * If there is a queue for playing, the searched video will be queued instead. * @param msg The message object that triggers the command. * @param {string} searchQuery Search string for the video/YouTube video URL/YouTube playlist URL */ play(msg, searchQuery) { return __awaiter(this, void 0, void 0, function* () { if (typeof searchQuery !== "string") return console.log("The query provided is not a string."); const youtube = this.youtube; const url = searchQuery ? searchQuery.replace(/<(.+)>/g, '$1') : ''; const voiceChannel = msg.member.voiceChannel; if (!voiceChannel) return msg.channel.send('I\'m sorry but you need to be in a voice channel to play music!').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); const permissions = voiceChannel.permissionsFor(msg.client.user); if (!permissions.has('CONNECT')) return msg.channel.send('I cannot connect to your voice channel, make sure I have the proper permissions!').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); if (!permissions.has('SPEAK')) return msg.channel.send('I cannot speak in this voice channel, make sure I have the proper permissions!').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); if (url.match(/^https?:\/\/(www.youtube.com|youtube.com)\/playlist(.*)$/)) { const playlist = yield youtube.getPlaylist(url); const videos = yield playlist.getVideos(); let video; for (video of Object.values(videos)) { const video2 = yield youtube.getVideoByID(video.id); yield musicFunctions.handleVideo(this.queueList, video2, msg, voiceChannel, this.settings.volume, this.settings.loop, false, true); } return msg.channel.send(`✅ Playlist: **${playlist.title}** has been added to the queue!`).then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); } else { try { var video = yield youtube.getVideo(url); } catch (error) { try { var videos = yield youtube.searchVideos(searchQuery, 10); let index = 0; msg.channel.send(` __**Song selection:**__ ${videos.map((video2) => { return `**${++index} -** ${video2.title}`; }).join('\n')} Please provide a value to select one of the search results ranging from 1-10. `).then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); try { var response = yield msg.channel.awaitMessages((msg2) => { return msg2.content > 0 && msg2.content < 11; }, { errors: ['time'], maxMatches: 1, time: this.settings.songChooseTimeout }); } catch (err) { console.error(err); return msg.channel.send('No or invalid value entered, cancelling video selection.').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); } const videoIndex = parseInt(response.first().content); var video = yield youtube.getVideoByID(videos[videoIndex - 1].id); } catch (err) { console.error(err); return msg.channel.send('🆘 I could not obtain any search results.').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); } } return musicFunctions.handleVideo(this.queueList, video, msg, voiceChannel, this.settings.volume, this.settings.loop); } }); } /** * Play the music requested in a voice channel with the command user. * * If there is a queue for playing, the searched video will be queued on top of others instead. * * The bot will return the command if a playlist URL is used. * @param msg The message object that triggers the command. * @param {string} searchQuery Search string for the video/YouTube video URL */ playTop(msg, searchQuery) { return __awaiter(this, void 0, void 0, function* () { var youtube = this.youtube; const url = searchQuery ? searchQuery.replace(/<(.+)>/g, '$1') : ''; const voiceChannel = msg.member.voiceChannel; if (!voiceChannel) return msg.channel.send('I\'m sorry but you need to be in a voice channel to play music!').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); const permissions = voiceChannel.permissionsFor(msg.client.user); if (!permissions.has('CONNECT')) return msg.channel.send('I cannot connect to your voice channel, make sure I have the proper permissions!').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); if (!permissions.has('SPEAK')) return msg.channel.send('I cannot speak in this voice channel, make sure I have the proper permissions!').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); if (url.match(/^https?:\/\/(www.youtube.com|youtube.com)\/playlist(.*)$/)) return msg.channel.send("You cannot use the playTop command with a playlist.").then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); else { try { var video = yield youtube.getVideo(url); } catch (error) { try { var videos = yield youtube.searchVideos(searchQuery, 10); let index = 0; msg.channel.send(` __**Song selection:**__ ${videos.map((video2) => { return `**${++index} -** ${video2.title}`; }).join('\n')} Please provide a value to select one of the search results ranging from 1-10. `).then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); try { var response = yield msg.channel.awaitMessages((msg2) => { return msg2.content > 0 && msg2.content < 11; }, { errors: ['time'], maxMatches: 1, time: this.settings.songChooseTimeout }); } catch (err) { console.error(err); return msg.channel.send('No or invalid value entered, cancelling video selection.').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); } const videoIndex = parseInt(response.first().content); var video = yield youtube.getVideoByID(videos[videoIndex - 1].id); } catch (err) { console.error(err); return msg.channel.send('🆘 I could not obtain any search results.').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); } } return musicFunctions.handleVideo(this.queueList, video, msg, voiceChannel, this.settings.volume, this.settings.loop, true); } }); } /** * Stops music and remove the music queue. * * This will also cause the bot to leave the voice channel. * @param msg The message object that triggers the command. */ stop(msg) { const queue = this.queueList; const serverQueue = queue.get(msg.guild.id); if (!msg.member.voiceChannel) return msg.channel.send('You are not in a voice channel!').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); if (!serverQueue) return msg.channel.send('There is nothing playing that I could stop for you.').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); serverQueue.songs = []; serverQueue.connection.dispatcher.end("Bot got stopped."); } /** * Skips the music which the bot is now playing. * * If this is the last song in the queue, * this will also cause the bot to leave the voice channel. * @param msg The message object that triggers the command. */ skip(msg) { const queue = this.queueList; const serverQueue = queue.get(msg.guild.id); if (!msg.member.voiceChannel) return msg.channel.send('You are not in a voice channel!').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); if (!serverQueue) return msg.channel.send('There is nothing playing that I could skip for you.').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); serverQueue.connection.dispatcher.end("Song got skipped."); } /** * Displays the music queue. * @param msg The message object that triggers the command. */ showQueue(msg) { const queue = this.queueList; const serverQueue = queue.get(msg.guild.id); if (!serverQueue) return msg.channel.send('There is nothing playing.').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); var index = 0; var songArray = serverQueue.songs.map((song) => { return `**${++index}-** [${song.title}](${song.url})`; }); musicFunctions.addMusicQueueField(msg, songArray, queue).then((results) => __awaiter(this, void 0, void 0, function* () { for (let i = 0; i < results.length; i++) { yield new Promise((r) => { return setTimeout(r, 500); }); const element = results[i]; msg.channel.send(element).then((m) => { return m.delete(30000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); } })); } /** * Displays the music now playing. * @param msg The message object that triggers the command. */ nowPlaying(msg) { const queue = this.queueList; const serverQueue = queue.get(msg.guild.id); if (!serverQueue) return msg.channel.send('There is nothing playing.').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); var embed = new discord_js_1.RichEmbed() .setColor(Math.floor(Math.random() * 16777214) + 1) .setTimestamp() .setThumbnail(serverQueue.songs[0].icon) .addField(`Now playing in ${msg.guild.name}:`, `[**${serverQueue.songs[0].title}**](${serverQueue.songs[0].url})`) .setFooter(`Requested by ${msg.author.username}`, msg.author.avatarURL); return msg.channel.send(embed).then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); } /** * Removes a certain song in the music queue. * * You cannot remove the first song in the queue with this method. * @param msg The message object that triggers the command. * @param {number} queueIndex The index for the song in the queue. * @example * // Song queue : * // 1. National Anthem of USSR, * // 2. Do you hear the people sing? * * // I wanted to remove the song "Do you hear the people sing?". * musicClient.remove(2) * // New song queue : * // 1. National Anthem of USSR */ remove(msg, queueIndex) { if (typeof queueIndex !== "number") return console.log("The query provided is not a number."); const queue = this.queueList; const serverQueue = queue.get(msg.guild.id); if (!serverQueue) return msg.channel.send('There is nothing playing.').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); var deleteIndex = queueIndex - 1; if (deleteIndex === 0) return msg.channel.send(`You cannot remove the song that is now playing. To remove it, use skip command instead.`).then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); var removed = serverQueue.songs.splice(deleteIndex, 1); msg.channel.send(`**${removed[0].title}** has been removed from the queue.`).then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); var index = 0; var songArray = serverQueue.songs.map((song) => { return `**${++index}-** [${song.title}](${song.url})`; }); musicFunctions.addMusicQueueField(msg, songArray, queue).then((results) => __awaiter(this, void 0, void 0, function* () { for (let i = 0; i < results.length; i++) { yield new Promise((r) => { return setTimeout(r, 500); }); const element = results[i]; msg.channel.send(element).then((m) => { return m.delete(30000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); } })); } /** * Repeats the first song in queue. * * Looping the song queue will be disabled upon usage of this command. * @param msg The message object that triggers the command. */ repeat(msg) { const serverQueue = this.queueList.get(msg.guild.id); if (!serverQueue) return msg.channel.send('There is nothing playing.').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); if (serverQueue.repeat === false) { serverQueue.repeat = true; msg.channel.send("The first song in the queue is now being repeated.").then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); if (serverQueue.loop === true) { serverQueue.loop = false; msg.channel.send("Looping has been disabled to avoid confusion.").then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); } } else { serverQueue.repeat = false; msg.channel.send("The first song in the queue is no longer being repeated.").then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); } } /** * Loops the whole song queue. * * Repeat a single song will be disabled upon usage of this command. * @param msg The message object that triggers the command. */ loop(msg) { const queue = this.queueList; const serverQueue = queue.get(msg.guild.id); if (!serverQueue) return msg.channel.send('There is nothing playing.').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); if (serverQueue.loop === false) { serverQueue.loop = true; msg.channel.send("The song queue is now being looped.").then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); if (serverQueue.repeat === true) { serverQueue.repeat = false; msg.channel.send("Repeating the first song has been disabled to avoid confusion.").then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); } } else { serverQueue.loop = false; msg.channel.send("The song queue is no longer being looped.").then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); } } /** * Shuffles the whole music queue. * @param msg The message object that triggers the command. */ shuffle(msg) { const queue = this.queueList; const serverQueue = queue.get(msg.guild.id); if (!serverQueue) return msg.channel.send('There is nothing playing.').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); musicFunctions.shuffleArray(serverQueue.songs); var index = 0; var songArray = serverQueue.songs.map((song) => { return `**${++index}-** [${song.title}](${song.url})`; }); musicFunctions.addMusicQueueField(msg, songArray, queue).then((results) => __awaiter(this, void 0, void 0, function* () { for (let i = 0; i < results.length; i++) { yield new Promise((r) => { return setTimeout(r, 500); }); const element = results[i]; msg.channel.send(element).then((m) => { return m.delete(30000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); } })); msg.channel.send("Song queue has been shuffled.").then((m) => { return m.delete(30000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); } /** * Changes the volume of the music. * * The default volume is 20/100, which is safe to turn the music bot volume in discord to 100%. * Tuning up the volume higher than 50 is not recommended. * * Any negative numbers in the volume will only cause the bot to display current volume. * * This will NOT cause any performance issues as stated from some music bot developers. * @param msg The message object that triggers the command. * @param {number} volume A number to change the volume based on 100. */ volume(msg, volume = -1) { if (typeof volume !== "number") return msg.channel.send("The volume provided is not a number").then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); const queue = this.queueList; const serverQueue = queue.get(msg.guild.id); if (!msg.member.voiceChannel) return msg.channel.send('You are not in a voice channel!').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); if (!serverQueue) return msg.channel.send('There is nothing playing.').then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); if (volume > 100 && this.settings.earProtections === true) return msg.channel.send(`I think you still need your ears for listening to more beautiful music.\nThe volume limit was capped on 100. The volume has not been modified. The current volume is ${serverQueue.volume}.`); if (volume > 100) msg.channel.send("WARNING : THE MUSIC WILL PLAY IN AN EXTREMELY LOUD VOLUME.").then((m) => { return m.delete(15000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); if (volume < 0) return msg.channel.send(`The current volume is ${serverQueue.volume}.`).then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); serverQueue.volume = volume; serverQueue.connection.dispatcher.setVolumeLogarithmic(volume / 100); return msg.channel.send(`I set the volume to: **${volume}**`).then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); } /** * Pause the music playback. * @param msg The message object that triggers the command. */ pause(msg) { const queue = this.queueList; const serverQueue = queue.get(msg.guild.id); if (serverQueue.paused === false) { serverQueue.paused = true; return msg.channel.send("The song playback has been stopped."); } else { return msg.channel.send("The song playback is already stopped."); } } /** * Resumes the music playback. * @param msg The message object that triggers the command. */ resume(msg) { const queue = this.queueList; const serverQueue = queue.get(msg.guild.id); if (serverQueue.paused === true) { serverQueue.paused = false; return msg.channel.send("The song playback has been resumed."); } else { return msg.channel.send("The song playback is not stopped."); } } } exports.musicClient = musicClient; const musicFunctions = { addMusicQueueField(msg, content, queue) { return __awaiter(this, void 0, void 0, function* () { const serverQueue = queue.get(msg.guild.id); var toSendEmbed = []; var color = Math.floor(Math.random() * 16777214) + 1; let i = 0; while (i < content.length) { var embed = new discord_js_1.RichEmbed(); let index = 0; while (i < content.length && index < 25) { var list = []; const element0 = content[i]; index++; i++; const element1 = content[i]; index++; i++; const element2 = content[i]; index++; i++; const element3 = content[i]; index++; i++; const element4 = content[i]; index++; i++; list.push(element0); element1 ? list.push(element1) : console.log("Empty element"); element1 ? list.push(element2) : console.log("Empty element"); element1 ? list.push(element3) : console.log("Empty element"); element1 ? list.push(element4) : console.log("Empty element"); if (i < 25) { embed.setTitle(`Song queue for ${msg.guild.name}`); embed.setDescription(`There are ${serverQueue.songs.length} songs in total.`); embed.setAuthor(msg.author.username, msg.author.avatarURL); } embed.setTimestamp(); embed.setFooter(`Now playing : ${serverQueue.songs[0].title}`); embed.addField("** **", list.join("\n")); embed.setColor(color); } toSendEmbed.push(embed); } return toSendEmbed; }); }, handleVideo(queueList, video, msg, voiceChannel, musicVolume = 20, loopQueue = false, top = false, playlist = false) { return __awaiter(this, void 0, void 0, function* () { const serverQueue = queueList.get(msg.guild.id); const song = { guild: msg.guild.name, icon: video.thumbnails.default.url, id: video.id, length: { hrs: video.duration.hours, mins: video.duration.minutes, secs: video.duration.seconds }, title: video.title, url: `https://www.youtube.com/watch?v=${video.id}` }; if (!serverQueue) { var queueConstruct = { connection: null, loop: loopQueue, paused: true, repeat: false, songs: [], textChannel: msg.channel, voiceChannel, volume: musicVolume }; queueList.set(msg.guild.id, queueConstruct); queueConstruct.songs.push(song); console.log("Song added to queue."); try { var connection = yield voiceChannel.join(); queueConstruct.connection = connection; musicFunctions.playMusic(msg.guild, queueConstruct.songs[0], queueList); } catch (error) { console.error(`I could not join the voice channel: ${error}`); queueList.delete(msg.guild.id); return msg.channel.send(`I could not join the voice channel: ${error}`).then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); } } else if (top) { serverQueue.songs.splice(1, 0, song); if (playlist) return undefined; else return msg.channel.send(`✅ **${song.title}** has been added to the queue!`).then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); } else { serverQueue.songs.push(song); if (playlist) return undefined; else return msg.channel.send(`✅ **${song.title}** has been added to the queue!`).then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); } return undefined; }); }, playMusic(guild, song, queueList) { const serverQueue = queueList.get(guild.id); try { if (!song) { serverQueue.voiceChannel.leave(); queueList.delete(guild.id); return; } } catch (error) { console.log(error); } const dispatcher = serverQueue.connection.playStream(ytdl(song.url, { filter: "audioonly", highWaterMark: 1024 * 512, quality: "highestaudio" })).on('end', (reason) => { if (serverQueue.loop === true) { console.log("Song ended, but looped"); var toPush = serverQueue.songs[0]; serverQueue.songs.push(toPush); serverQueue.songs.shift(); musicFunctions.playMusic(guild, serverQueue.songs[0], queueList); } else if (serverQueue.repeat === true) { console.log("Song ended, but repeated"); musicFunctions.playMusic(guild, serverQueue.songs[0], queueList); } else { if (reason === 'Stream is not generating quickly enough.') console.log('Song ended.'); else console.log(`${reason}`); serverQueue.songs.shift(); musicFunctions.playMusic(guild, serverQueue.songs[0], queueList); } }).on('error', (error) => { return console.error(error); }); dispatcher.setVolumeLogarithmic(serverQueue.volume / 100); serverQueue.textChannel.send(`🎶 Start playing: **${song.title}**`).then((m) => { return m.delete(10000).catch((reason) => { console.log(`Attempting to delete a deleted message (Which is impossible)`); }); }); }, shuffleArray(array) { let temp = array[0]; array.splice(0, 1); var i; var j; var x; for (i = array.length - 1; i > 0; i--) { j = Math.floor(Math.random() * (i + 1)); x = array[i]; array[i] = array[j]; array[j] = x; } array.unshift(temp); temp = []; return array; } }; module.exports = musicClient;