Source: builders/Player.js

require('dotenv').config();
const tp = require('../functions/TP.js');
const createRoute = require('../functions/createRoute.js');
const Events = require('../types/Events.js');
const path = require('node:path');
const fs = require('node:fs');

const {
  joinVoiceChannel,
  AudioPlayer,
  AudioPlayerStatus,
  createAudioPlayer,
  createAudioResource,
  VoiceConnection,
  StreamType
} = require('@discordjs/voice');
const {
  createWriteStream,
  createReadStream,
  unlink
} = fs;

const SoundCloudClient = require('./SoundCloudClient.js');

/**
 * It is recommended to define as "client.player" in your bot.
 * @class
 */
class Player extends AudioPlayer {
  constructor (options = {
    debug: false
  }) {
    super(options);
    if (!fs.existsSync(createRoute())) fs.mkdirSync(createRoute());
    
    this.client = new SoundCloudClient();
    this.connections = [];
    this.players = [];
  }
  
  /**
   * Get the guild player playlist.
   * @param {Object} interaction Interaction object (Message|Interaction).
   */
  getPlaylist (interaction) {
    return cb((this.players
      .find(x => x.metadata.guildId === interaction.guild.id) || {
        playlist: []}).playlist);
  }
  
  /**
   * Find or create a connection.
   * @param {Object} PlayerConnectionOptions PlayerConnectionOptions.
   */
  async connect (PlayerConnectionOptions) {
    let find = (this.connections.find(x => x.joinConfig.guildId === PlayerConnectionOptions.guildId));
    if (find) {
      return find;
    } else {
      let connection = joinVoiceChannel(PlayerConnectionOptions);

      this.connections.push(connection);
      return connection;
    }
  }
  
  /**
   * Get or create a AudioPlayer.
   * @param {Object} interaction Interaction object (Message|Interaction).
   */
  async createPlayer (interaction) {
    let find = (this.players.find(x => x.guildId === interaction.guild.id));
    if (find) {
      return find.player;
    } else {
      let player = createAudioPlayer();
      player.metadata = {};
      player.playlist = [];
      player.metadata.guildId = interaction.guild.id;

      player
      .on(AudioPlayerStatus.Idle, (oldStatus) => {
        if (oldStatus.status !== AudioPlayerStatus.Idle && player.playlist.length >= 1) {
          let nextSong = player.playlist.shift();
          this.play(nextSong, interaction);
          player.playlist = player.playlist
          .filter(x => x !== nextSong);
        }
        if (player.playlist.length === 0) {
          let myConnection = this.connections
          .find(x => x.joinConfig.guildId === interaction.guild.id);
          if (myConnection) {
            myConnection.disconnect();
            this
            .emit(Events.END_SONG, interaction.channel);
            this
            .connections = this.connections
            .filter(x => x.joinConfig.guildId !== interaction.guild.id);
          }
        }
      });
      console.log(player)
      this.players.push(player);
      return player;
    }
  }
  
  /**
   * Play a song.
   * @param {String} query Song title or playlist link.
   * @param {Object} interaction Interaction object (Message|Interaction).
   */
  async play (query,
    interaction) {
    this
    .search(query,
      interaction,
      async (route, song) => {
        let myConnection = this.connections.find(x => x.joinConfig.guildId === interaction.guild.id);
        if (!myConnection) myConnection = await this.connect({
          channelId: interaction.member.voice.channelId,
          guildId: interaction.guild.id,
          adapterCreator: interaction.guild.voiceAdapterCreator
        });
        let myPlayer = this.players.find(x => x.metadata.guildId === interaction.guild.id);
        if (!myPlayer) myPlayer = await this.createPlayer(interaction);

        let Resource = createAudioResource(route, {
          inlineVolume: true
        });


        Resource.volume.setVolume(0.5);

        myPlayer.play(Resource);
        myConnection.subscribe(myPlayer);
        this.emit(Events.PLAY_SONG,
          interaction.channel,
          song);
      });

  }
  
  /**
   * Search a song or playlist.
   * @param {String} query Song title or playlist link.
   * @param {Object} interaction Interaction object (Message|Interaction).
   * @param {search~Callback} cb - The callback that handles the response
   */
  async search (query,
    interaction,
    cb) {
    console.log('Passed Query:',
      query)
    if (query.startsWith('https://soundcloud.com/') || query.startsWith('https://m.soundcloud.com/')) {
      this.searchPlaylist(query, interaction);
    } else {
      this.client.search(query,
        'track')
      .then(async ArrayOfTracks => {
        let targetTrack = ArrayOfTracks.shift();

        if (!targetTrack) return false;

        this.client.getSongInfo(targetTrack.url)
        .then(async (song) => {
          let stream = await song
          .downloadProgressive();

          let writer = stream.pipe(fs.createWriteStream(createRoute(interaction.guild.id)));
          console.log('152-', song);
          song.title = song.title.replace('.mp3', '');
          song.duration = tp(song.duration);
          writer.on('finish', () => cb(createRoute(interaction.guild.id), song));
        })
        .catch(err => console.log(err));
      })
      .catch(err => console.log(err));
    }
  }
  /**
   * @callback search~Callback
   * @param {String} FilePath
   * @param {string} SongInfo
   */

  /**
   * Search a playlist.
   * @param {String} query playlist link or title.
   * @param {Object} interaction Interaction object (Message|Interaction).
   * @param {searchPlaylist~Callback} cb - The callback that handles the response
   */
  async searchPlaylist (query,
    interaction,
    cb) {
    let myPlayer = this.players.find(x => x.metadata.guildId === interaction.guild.id) || await this.createPlayer(interaction);

    if (query.startsWith('https://soundcloud.com/') || query.startsWith('https://m.soundcloud.com/')) {

      this.client.getPlaylist(query.replace('https://m.soundcloud.com/', 'https://soundcloud.com/'))
      .then(async x => {
        await x.tracks.forEach(s => {
          myPlayer.playlist.push(s.title);
        });

        this.emit(Events.ADD_PLAYLIST, interaction.channel, x);
        /*interaction.reply('The songs on the list have been added to the list on the server.');*/

        let npsong = myPlayer.playlist.shift();
        this.play(npsong, interaction);
        myPlayer.playlist = myPlayer.playlist.filter(x => x !== npsong);
      });
    } else {
      this.client
      .search(query,
        'playlist')
      .then((result) => {
        this.client.getPlaylist(result[0].url)
        .then(async x => {
          await x.tracks.forEach(s => {
            myPlayer.playlist.push(s.title);
          });

          this.emit(Events.ADD_PLAYLIST, interaction.channel, x);
          /*interaction.reply('The songs on the list have been added to the list on the server.');*/

          let npsong = myPlayer.playlist.shift();
          this.play(npsong, interaction);
          myPlayer.playlist = myPlayer.playlist.filter(x => x !== npsong);
        });
      });
    }
  }
  
  /**
   * Pause music playback.
   * @param {Object} interaction Interaction object (Message|Interaction).
   */
  pause (interaction) {
    let myPlayer = this.players.find(x => x.metadata.guildId === interaction.guild.id);
    myPlayer.pause();
    console.log(myPlayer.playlist);
    this.emit(Events.PAUSE, interaction.channel);
  }
  
  /**
   * Resumes music playback.
   * @param {Object} interaction Interaction object (Message|Interaction).
   */
  async resume (interaction) {
    let myPlayer = this.players.find(x => x.metadata.guildId === interaction.guild.id);

    myPlayer.unpause();
    console.log(myPlayer.playlist);
    this.emit(Events.RESUME, interaction.channel);
  }
  /**
   * Skip to the next song.
   * @param {Object} interaction Interaction object (Message|Interaction).
   */
  async skip (interaction) {
    let myPlayer = this.players
    .find(x => x.metadata.guildId === interaction.guild.id) || await this.createPlayer(interaction);
    console.log('PL:', myPlayer.playlist);
    let npsong = myPlayer.playlist.shift();

    if (typeof npsong === "string") {
      this.emit(Events.SKIP, (interaction.channel));
      this.play(npsong, interaction);
      myPlayer.playlist = myPlayer.playlist.filter(x => x !== npsong);
    } else {
      myPlayer.emit(AudioPlayerStatus.Idle, {
        status: AudioPlayerStatus.Idle
      });
    }
  }
}

module.exports = Player;