Advertisement
AdhityaRimba

RepVRT Music

Aug 18th, 2019
682
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. const { Command } = require('discord.js-commando');
  2. const { MessageEmbed } = require('discord.js');
  3. const Youtube = require('simple-youtube-api');
  4. const ytdl = require('ytdl-core');
  5. const youtube = new Youtube(youtubeApiKey); // insert here your Youtube API key, you can also store it as an environment variable or in a config.json
  6.  
  7. var queue = []; // in this array we will store songs in queue
  8. var isPlaying; // we will use this variable to determine if a song is playing
  9.  
  10. module.exports = class PlayCommand extends Command {
  11.   constructor(client) {
  12.     super(client, {
  13.       name: 'play',
  14.       aliases: ['play-song', 'add'],
  15.       memberName: 'play',
  16.       group: 'music', // this means the folder the file is inside
  17.       description: 'Play any song from youtube',
  18.       guildOnly: true, // make this command available only in servers not dm's
  19.       clientPermissions: ['SPEAK', 'CONNECT'],
  20.       throttling: { // limit the uses so users don't spam the command
  21.         usages: 2,
  22.         duration: 5
  23.       },
  24.       args: [
  25.         {
  26.           key: 'query', // here we name the variable that will hold the input
  27.           prompt: 'What song would you like to listen to?', // send this msg if
  28.           // the user hasn't provided any arg or if the arg was not a string
  29.           type: 'string', // only permit string inputs
  30.           validate: query => query.length > 0 && query.length < 200
  31.         }
  32.       ]
  33.     });
  34.   }
  35. async run(message, { query }) {
  36.     // determine if the user is in a voice channel when calling the command
  37.     var voiceChannel = message.member.voice.channel;
  38.     if (!voiceChannel) return message.say('Join a channel and try again');
  39.     // if not, return the above message
  40. if (query.match(/^(http(s)?:\/\/)?((w){3}.)?youtu(be|.be)?(\.com)?\/.+/)) {
  41.       const url = query;
  42.       try { // using try catch because there are many api calls and as a result -async await usage
  43.         /*
  44.         the 'replace' and 'split' methods create an array that looks
  45.         like this: [ 'https://www.youtube.com/watch?', 'v=', 'dQw4w9WgXcQ' ]
  46.         then we declare an 'id' variable and assign it to the 3rd element
  47.         */
  48.         query = query
  49.           .replace(/(>|<)/gi, '')
  50.           .split(/(vi\/|v=|\/v\/|youtu\.be\/|\/embed\/)/);
  51.         const id = query[2].split(/[^0-9a-z_\-]/i)[0];
  52.         const video = await youtube.getVideoByID(id); // getting a video object by calling
  53.         // the getVideoByID provided to us by simple-youtube-api
  54.         /*
  55.         I didn't test live streams yet to determine if the bot is capable of
  56.         playing them, also you can remove the second if statement if you want,
  57.         I chose to keep it for now.
  58.         */
  59.         if (video.raw.snippet.liveBroadcastContent === 'live')
  60.           return message.say("I don't support live streams!");
  61.         if (video.duration.hours !== 0)
  62.           return message.say('I cannot play videos longer than 1 hour');
  63.         /*
  64.         construct the song object, it has url, title and voiceChannel
  65.         object that contains methods which are critical to us later on(join,
  66.         dispatcher etc..)
  67.         */
  68.         const title = video.title;
  69.         const song = {
  70.           url,
  71.           title,
  72.           voiceChannel
  73.         };
  74.         // this if statement limits the queue size, can be removed if you wish
  75.         if (queue.length > 6) {
  76.           return message.say(
  77.             'There are too many songs in the queue already, skip or wait a bit'
  78.           );
  79.         }
  80.         queue.push(song); // push the song object to queue
  81.         /*
  82.         If there is no song playing, call the playSong method(we will see it
  83.         later on :)
  84.         else if there is a song playing, return a msg that says the song was
  85.         added to queue
  86.         */
  87.         if (isPlaying == false || typeof isPlaying == 'undefined') {
  88.           isPlaying = true;
  89.           return playSong(queue, message);
  90.         } else if (isPlaying == true) {
  91.           return message.say(`${song.title} added to queue`);
  92.         }
  93.       // catches errors from getVideoByID method
  94.       } catch (err) {
  95.         console.error(err);
  96.         return message.say('Something went wrong, please try later');
  97.       }
  98.     }
  99. try {
  100.       /*
  101.       call 'searchVideos' method to get a list of 5 video objects that match the
  102.       query. Then create an array of the 5 videos numbered from 1 to 5 with their
  103.       titles.
  104.       */
  105.       const videos = await youtube.searchVideos(query, 5);
  106.       const vidNameArr = [];
  107.       for (let i = 0; i < videos.length; i++) {
  108.         vidNameArr.push(`${i + 1}: ${videos[i].title}`);
  109.       }
  110.       vidNameArr.push('exit');
  111.       /* construct a message embed that will be displayed to the chat, it
  112.       contains the song titles fetched using 'searchVideos'.
  113.       */
  114.          const embed = new MessageEmbed()
  115.         .setColor('#e9f931')
  116.         .setTitle('Choose a song by commenting a number between 1 and 5')
  117.         .addField('Song 1', vidNameArr[0])
  118.         .addField('Song 2', vidNameArr[1])
  119.         .addField('Song 3', vidNameArr[2])
  120.         .addField('Song 4', vidNameArr[3])
  121.         .addField('Song 5', vidNameArr[4])
  122.         .addField('Exit', 'exit');
  123.       var songEmbed = await message.say({ embed });
  124.       try {
  125.         /*
  126.         assign 'response' variable whatever the user types. The correct
  127.         responses are numbers between 1-5 or 'exit'. There is also a time limit
  128.         of 1 minute to respond.
  129.         */
  130.           var response = await message.channel.awaitMessages(
  131.           msg => (msg.content > 0 && msg.content < 6) || msg.content === 'exit',
  132.           {
  133.             max: 1,
  134.             maxProcessed: 1,
  135.             time: 60000,
  136.             errors: ['time']
  137.           }
  138.         );
  139.       } catch (err) { // catch errors from 'awaitMessages' and respond correctly
  140.         console.error(err);
  141.         songEmbed.delete()
  142.         return message.say(
  143.           'Please try again and enter a number between 1 and 5 or exit'
  144.         );
  145.       }
  146.       if (response.first().content === 'exit') return songEmbed.delete();
  147.       // assign videoIndex to the song number the user enters
  148.       const videoIndex = parseInt(response.first().content);
  149.       try {
  150.         // fetch the video object using 'getVideoByID'
  151.         var video = await youtube.getVideoByID(videos[videoIndex - 1].id);
  152.         if (video.raw.snippet.liveBroadcastContent === 'live')
  153.           return message.say("I don't support live streams!");
  154.       } catch (err) { // catch errors from 'getVideoByID'
  155.         console.error(err);
  156.         songEmbed.delete()
  157.         return message.say(
  158.           'An error has occured when trying to get the video ID from youtube'
  159.         );
  160.       }
  161.       const url = `https://www.youtube.com/watch?v=${video.raw.id}`;
  162.       const title = video.title;
  163.  
  164.       try {
  165.         let song = { // construct the song object
  166.           url,
  167.           title,
  168.           voiceChannel
  169.         };
  170.         if (queue.length > 6) {
  171.           return message.say(
  172.             'There are too many songs in the queue already, skip or wait a bit'
  173.           );
  174.         }
  175.         queue.push(song); // push the song object to queue
  176.         if (isPlaying == false || typeof isPlaying == 'undefined') {
  177.           isPlaying = true;
  178.           songEmbed.delete(); // delete the song list embed(so it wont spam chat)
  179.           playSong(queue, message); // play the song
  180.         } else if (isPlaying == true) {
  181.           songEmbed.delete();
  182.           return message.say(`${song.title} added to queue`);
  183.         }
  184.       } catch (err) {
  185.         console.error(err);
  186.         songEmbed.delete();
  187.         return message.say('queue process gone wrong');
  188.       }
  189.     } catch (err) { // catch errors from playSong()
  190.       console.error(err);
  191.       if (songEmbed) { // if the songEmbed wasn't deleted because of an error related to playSong() - delete it
  192.         songEmbed.delete();
  193.       }
  194.       return message.say(
  195.         'Something went wrong with searching the video you requested :('
  196.       );
  197.     }
  198.   }
  199. };
  200. function playSong(queue, message) {
  201.   let voiceChannel;
  202.   queue[0].voiceChannel
  203.     .join() // join the voice channel the user is in
  204.     .then(connection => {
  205.       const dispatcher = connection // sends voice packet data to the voice connection
  206.         .play(
  207.           ytdl(queue[0].url, { // provide ytdl library with the song url
  208.             volume: 0.1,
  209.             quality: 'highestaudio', // highest audio quality
  210.             highWaterMark: 1024 * 1024 * 10 // this line downloads part of the song before starting, it reduces stuttering
  211.           })
  212.         )
  213.         .on('start', () => { // event emitted when the song starts
  214.           /*
  215.           export dispatcher and queue to other music commands, check out my bot
  216.           repo on github to learn more
  217.           */
  218.           module.exports.dispatcher = dispatcher;
  219.           module.exports.queue = queue;
  220.           voiceChannel = queue[0].voiceChannel; // assign voiceChannel to the current one, incase the user moved channels in between songs
  221.           return message.say(
  222.             `:musical_note: Now playing: ${queue[0].title} :musical_note:`
  223.           );
  224.         })
  225.         .on('finish', () => { // event emitted when the song ends
  226.           queue.shift(); // remove the song from queue
  227.           if (queue.length >= 1) { // if the queue has more songs, continue playing
  228.             return playSong(queue, message);
  229.           } else { // else if the queue is empty, assign isPlaying to false and leave the channel
  230.             isPlaying = false;
  231.             return voiceChannel.leave();
  232.           }
  233.         })
  234.         .on('error', e => { // event emitted if an error occures
  235.           message.say('Cannot play song');
  236.           return console.log(e);
  237.         });
  238.     })
  239.     .catch(err => { // catches dispatcher errors
  240.       return console.log(err);
  241.     });
  242. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement