<commit_before>/**
 * @file play command
 * @author <NAME> (a.k.a k3rn31p4nic)
 * @license GPL-3.0
 */

const util = require('util');
const youtubeDL = require('youtube-dl');
const getSongInfo = util.promisify(youtubeDL.getInfo);

exports.exec = async (Bastion, message, args) => {
  try {
    if (!message.guild.music.enabled) {
      if (Bastion.user.id === '267035345537728512') {
        return Bastion.emit('error', '', Bastion.i18n.error(message.guild.language, 'musicDisabledPublic'), message.channel);
      }
      return Bastion.emit('error', '', Bastion.i18n.error(message.guild.language, 'musicDisabled'), message.channel);
    }

    if (message.guild.music.textChannelID && message.guild.music.textChannelID !== message.channel.id) {
      return Bastion.log.info('Music channels have been set, so music commands will only work in the Music Text Channel.');
    }


    if (!args.song) {
      /**
      * The command was ran with invalid parameters.
      * @fires commandUsage
      */
      return Bastion.emit('commandUsage', message, this.help);
    }


    let voiceConnection = message.guild.voiceConnection, voiceChannel, textChannel, vcStats;

    if (voiceConnection) {
      voiceChannel = voiceConnection.channel;
      textChannel = message.guild.music.textChannel || message.channel;

      vcStats = Bastion.i18n.error(message.guild.language, 'userNoSameVC', message.author.tag);
    }
    else {
      if (message.guild.music.textChannelID && message.guild.music.voiceChannelID) {
        voiceChannel = message.guild.channels.filter(c => c.type === 'voice').get(message.guild.music.voiceChannelID);
        textChannel = message.guild.channels.filter(c => c.type === 'text').get(message.guild.music.textChannelID);

        if (!voiceChannel || !textChannel) {
          return Bastion.emit('error', '', Bastion.i18n.error(message.guild.language, 'invalidMusicChannel'), message.channel);
        }

        vcStats = Bastion.i18n.error(message.guild.language, 'userNoMusicChannel', message.author.tag, voiceChannel.name);
      }
      else if (Bastion.credentials.ownerId.includes(message.author.id) || message.member.roles.has(message.guild.music.masterRoleID)) {
        voiceChannel = message.member.voiceChannel;
        textChannel = message.channel;

        if (!voiceChannel) {
          return Bastion.emit('error', '', Bastion.i18n.error(message.guild.language, 'userNoVC', message.author.tag), message.channel);
        }
      }
      else {
        return Bastion.emit('error', '', Bastion.i18n.error(message.guild.language, 'musicChannelNotFound'), message.channel);
      }

      if (!voiceChannel.joinable) {
        return Bastion.emit('error', '', Bastion.i18n.error(message.guild.language, 'noPermission', 'join', voiceChannel.name), message.channel);
      }

      voiceConnection = await voiceChannel.join();
    }


    if (!voiceChannel.speakable) {
      return Bastion.emit('error', '', Bastion.i18n.error(message.guild.language, 'noPermission', 'speak', `in ${voiceChannel.name}`), message.channel);
    }
    if (textChannel.id !== message.channel.id) {
      return Bastion.log.info(`Music commands will only work in the ${textChannel.name} text channel for this session.`);
    }
    if (!voiceChannel.members.get(message.author.id)) {
      return Bastion.emit('error', '', vcStats, message.channel);
    }

    message.guild.me.setMute(false).catch(() => {});
    message.guild.me.setDeaf(true).catch(() => {});


    message.guild.music.voiceChannel = voiceChannel;
    message.guild.music.textChannel = textChannel;


    if (!message.guild.music.hasOwnProperty('songs')) {
      message.guild.music.songs = [];
    }
    if (!message.guild.music.hasOwnProperty('playing')) {
      message.guild.music.playing = false;
    }
    if (!message.guild.music.hasOwnProperty('repeat')) {
      message.guild.music.repeat = false;
    }
    if (!message.guild.music.hasOwnProperty('skipVotes')) {
      message.guild.music.skipVotes = [];
    }


    args.song = args.song.join(' ');
    let songURL = /^http[s]?:[/]{2}(?:[a-z0-9](?:[a-zA-Z0-9-]{0,249}(?:[a-zA-Z0-9]))?\.?){1,127}[-a-z0-9@:%_+.~#?&/=]{0,2045}$/i.test(args.song) ? args.song : `ytsearch:${args.song}`;

    let youtubeDLOptions = [
      '--quiet',
      '--ignore-errors',
      '--simulate',
      '--no-warnings',
      '--format=bestaudio[protocol^=http]',
      `--user-agent=BastionDiscordBot/v${Bastion.package.version} (https://bastionbot.org)`,
      '--referer=https://bastionbot.org',
      '--youtube-skip-dash-manifest'
    ];

    let songInfo = await getSongInfo(songURL, youtubeDLOptions);

    if (!songInfo.format_id || songInfo.format_id.startsWith('0')) {
      Bastion.log.error(songInfo);
      return Bastion.emit('error', '', Bastion.i18n.error(message.guild.language, 'notFound', 'result'), message.channel);
    }

    message.guild.music.songs.push({
      url: songInfo.url,
      id: songInfo.id,
      title: songInfo.title,
      thumbnail: songInfo.thumbnail,
      duration: songInfo.duration,
      requester: message.author.tag
    });

    textChannel.send({
      embed: {
        color: Bastion.colors.GREEN,
        title: 'Added to the queue',
        url: songInfo.id ? `https://youtu.be/${songInfo.id}` : '',
        description: songInfo.title,
        thumbnail: {
          url: songInfo.thumbnail
        },
        footer: {
          text: `Position: ${message.guild.music.songs.length} • Duration: ${songInfo.duration || 'N/A'} • Requester: ${message.author.tag}`
        }
      }
    }).catch(e => {
      Bastion.log.error(e);
    });


    if (!message.guild.music.playing) {
      startStreamDispatcher(message.guild, voiceConnection);
    }
  }
  catch (e) {
    Bastion.log.error(e);
    return Bastion.emit('error', '', Bastion.i18n.error(message.guild.language, 'notFound', 'result'), message.channel);
  }
};

exports.config = {
  aliases: [],
  enabled: true,
  argsDefinitions: [
    { name: 'song', type: String, multiple: true, defaultOption: true }
  ]
};

exports.help = {
  name: 'play',
  botPermission: '',
  userTextPermission: '',
  userVoicePermission: '',
  usage: 'play < SONG NAME | SONG_LINK >',
  example: [ 'play Shape of you', 'play https://www.youtube.com/watch?v=fNVUTgd4pio' ]<commit_msg>commands: Add YouTube playlist support to new play command<commit_after>/**
 * @file play command
 * @author <NAME> (a.k.a k3rn31p4nic)
 * @license GPL-3.0
 */

const util = require('util');
const youtubeDL = require('youtube-dl');
const getSongInfo = util.promisify(youtubeDL.getInfo);

exports.exec = async (Bastion, message, args) => {
  try {
    if (!message.guild.music.enabled) {
      if (Bastion.user.id === '267035345537728512') {
        return Bastion.emit('error', '', Bastion.i18n.error(message.guild.language, 'musicDisabledPublic'), message.channel);
      }
      return Bastion.emit('error', '', Bastion.i18n.error(message.guild.language, 'musicDisabled'), message.channel);
    }

    if (message.guild.music.textChannelID && message.guild.music.textChannelID !== message.channel.id) {
      return Bastion.log.info('Music channels have been set, so music commands will only work in the Music Text Channel.');
    }


    if (!args.song) {
      /**
      * The command was ran with invalid parameters.
      * @fires commandUsage
      */
      return Bastion.emit('commandUsage', message, this.help);
    }


    let voiceConnection = message.guild.voiceConnection, voiceChannel, textChannel, vcStats;

    if (voiceConnection) {
      voiceChannel = voiceConnection.channel;
      textChannel = message.guild.music.textChannel || message.channel;

      vcStats = Bastion.i18n.error(message.guild.language, 'userNoSameVC', message.author.tag);
    }
    else {
      if (message.guild.music.textChannelID && message.guild.music.voiceChannelID) {
        voiceChannel = message.guild.channels.filter(c => c.type === 'voice').get(message.guild.music.voiceChannelID);
        textChannel = message.guild.channels.filter(c => c.type === 'text').get(message.guild.music.textChannelID);

        if (!voiceChannel || !textChannel) {
          return Bastion.emit('error', '', Bastion.i18n.error(message.guild.language, 'invalidMusicChannel'), message.channel);
        }

        vcStats = Bastion.i18n.error(message.guild.language, 'userNoMusicChannel', message.author.tag, voiceChannel.name);
      }
      else if (Bastion.credentials.ownerId.includes(message.author.id) || message.member.roles.has(message.guild.music.masterRoleID)) {
        voiceChannel = message.member.voiceChannel;
        textChannel = message.channel;

        if (!voiceChannel) {
          return Bastion.emit('error', '', Bastion.i18n.error(message.guild.language, 'userNoVC', message.author.tag), message.channel);
        }
      }
      else {
        return Bastion.emit('error', '', Bastion.i18n.error(message.guild.language, 'musicChannelNotFound'), message.channel);
      }

      if (!voiceChannel.joinable) {
        return Bastion.emit('error', '', Bastion.i18n.error(message.guild.language, 'noPermission', 'join', voiceChannel.name), message.channel);
      }

      voiceConnection = await voiceChannel.join();
    }


    if (!voiceChannel.speakable) {
      return Bastion.emit('error', '', Bastion.i18n.error(message.guild.language, 'noPermission', 'speak', `in ${voiceChannel.name}`), message.channel);
    }
    if (textChannel.id !== message.channel.id) {
      return Bastion.log.info(`Music commands will only work in the ${textChannel.name} text channel for this session.`);
    }
    if (!voiceChannel.members.get(message.author.id)) {
      return Bastion.emit('error', '', vcStats, message.channel);
    }

    message.guild.me.setMute(false).catch(() => {});
    message.guild.me.setDeaf(true).catch(() => {});


    message.guild.music.voiceChannel = voiceChannel;
    message.guild.music.textChannel = textChannel;


    if (!message.guild.music.hasOwnProperty('songs')) {
      message.guild.music.songs = [];
    }
    if (!message.guild.music.hasOwnProperty('playing')) {
      message.guild.music.playing = false;
    }
    if (!message.guild.music.hasOwnProperty('repeat')) {
      message.guild.music.repeat = false;
    }
    if (!message.guild.music.hasOwnProperty('skipVotes')) {
      message.guild.music.skipVotes = [];
    }

    let playlist = false;
    args.song = args.song.join(' ');
    if (/^http[s]?:[/]{2}(?:www\.)?youtube\.com\/(?:(?:playlist|watch)\?(?:[a-z0-9-_=&.]{1,2021})?list=[a-z0-9-_]{1,64}(?:[a-z0-9-_=&.]{1,2021})?)$/i.test(args.song)) {
      // YouTube Playlist
      playlist = true;
    }

    let songURL = /^http[s]?:[/]{2}(?:[a-z0-9](?:[a-zA-Z0-9-]{0,249}(?:[a-zA-Z0-9]))?\.?){1,127}[-a-z0-9@:%_+.~#?&/=]{0,2045}$/i.test(args.song) ? args.song : `ytsearch:${args.song}`;

    let youtubeDLOptions = [
      '--quiet',
      '--ignore-errors',
      '--simulate',
      '--no-warnings',
      '--format=bestaudio[protocol^=http]',
      `--user-agent=BastionDiscordBot/v${Bastion.package.version} (https://bastionbot.org)`,
      '--referer=https://bastionbot.org',
      '--youtube-skip-dash-manifest'
    ];

    let songInfo = await getSongInfo(
      songURL,
      playlist ? [ '--flat-playlist', '--yes-playlist' ].concat(youtubeDLOptions) : youtubeDLOptions
    );

    if (!songInfo || (!playlist && (!songInfo.format_id || songInfo.format_id.startsWith('0')))) {
      Bastion.log.error(songInfo);
      return Bastion.emit('error', '', Bastion.i18n.error(message.guild.language, 'notFound', 'result'), message.channel);
    }

    if (playlist) {
      songInfo.forEach(song => {
        message.guild.music.songs.push({
          url: `https://www.youtube.com/watch?v=${song.id}`,
          id: song.id,
          title: song.title,
          thumbnail: '',
          duration: 'N/A',
          requester: message.author.tag
        });
      });

      message.channel.send({
        embed: {
          color: Bastion.colors.GREEN,
          description: `Added ${songInfo.length} songs to the queue from the YouTube playlist.`
        }
      }).catch(e => {
        Bastion.log.error(e);
      });
    }
    else {
      message.guild.music.songs.push({
        url: songInfo.url,
        id: songInfo.id,
        title: songInfo.title,
        thumbnail: songInfo.thumbnail,
        duration: songInfo.duration,
        requester: message.author.tag
      });

      textChannel.send({
        embed: {
          color: Bastion.colors.GREEN,
          title: 'Added to the queue',
          url: songInfo.id ? `https://youtu.be/${songInfo.id}` : '',
          description: songInfo.title,
          thumbnail: {
            url: songInfo.thumbnail
          },
          footer: {
            text: `Position: ${message.guild.music.songs.length} • Duration: ${songInfo.duration || 'N/A'} • Requester: ${message.author.tag}`
          }
        }
      }).catch(e => {
        Bastion.log.error(e);
      });
    }


    if (!message.guild.music.playing) {
      startStreamDispatcher(message.guild, voiceConnection);
    }
  }
  catch (e) {
    Bastion.log.error(e);
    return Bastion.emit('error', '', Bastion.i18n.error(message.guild.language, 'notFound', 'result'), message.channel);
  }
};

exports.config = {
  aliases: [],
  enabled: true,
  argsDefinitions: [
    { name: 'song', type: String, multiple: true, defaultOption: true }
  ]
};

exports.help = {
  name: 'play',
  botPermission: '',
  userTextPermission: '',
  userVoicePermission: '',
  usage: 'play < SONG NAME | SONG_LINK | YOUTUBE_PLAYLIST_LINK >',
  example: [ 'play Shape of you', 'play https://www.youtube.com/watch?v=fNVUTgd4pio' ]
{'fraction_non_alphanumeric': 0.16962654388657458, 'fraction_numerical': 0.01169334210333991, 'mean_word_length': 2.992996790195506, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 21, 'https://': 9, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4285438', 'n_tokens_mistral': 4682, 'n_tokens_neox': 4458, 'n_words': 708}