main.js

/**
 * @typedef {Object} SpotifyAuth The auth codes for spotify
 * @property {string} clientId The client ID of your spotify application
 * @property {string} clientSecret The client secret of your spotify application
 * @property {string} refresh_token the refresh token for your spotify application
 * @property {string|undefined} market the market to search on spotify
 */
/**
 * @typedef {Object} AuthCodes The auth codes for the client
 * @property {SpotifyAuth} spotify The Spotify Auth codes
 */

const EventEmiter = require('node:events')
const regexpConstants = require('./constants/regex')
const search = require('./utils/search')
const play = require('play-dl')
const QueueBuilder = require('./utils/Queue.js')
const { getVoiceConnection } = require('@discordjs/voice')

class Player extends EventEmiter {
    /**
     * Create a new Dismusic Player
     * @param {Object} client The discord.js Client you want to use
     * @param {AuthCodes|undefined} authCodes The auth code for spotify
     */
    constructor(client, authCodes) {
        if(!client) throw new Error('[ Dismusic Error ] A valid discord client is required to create a player')
        super()
        this.client = client
        this.queues = {}
        this.queues.players = {}
        if(authCodes?.spotify) {
            play.getFreeClientID().then(async (id) => {
                play.setToken({
                    soundcloud: {
                        client_id: id
                    },
                    spotify: {
                        client_id: authCodes.clientId,
                        client_secret: authCodes.clientSecret,
                        refresh_token: authCodes.refresh_token,
                        market: authCodes?.market || 'US'
                    }
                })
                this.hasSpotifyToken = true
            })
        } else {
            console.log('[ Dismusic Warning ] Spotify data was not provided! This is required to fall back to play-dl when spotify-url-info returns undefined')
        }
    }
    /**
     * Search a track
     * @param {String} query The query you want to search
     * @param {String} engine The place where you want to search. Can be 'YouTube' or 'SoundCloud'
     */
    async search(query, engine) {
        if(!query) throw new Error('[ Dismusic Error ] A valid query must be provided')
        if(typeof query != 'string') throw new Error(`[ Dismusic Error ] Query must be a type of String. Got ${typeof query}`)
        // validate the string
        const isYTUrl = String(query).match(regexpConstants.youtube)
        const isSpotifyUrl = String(query).match(regexpConstants.spotify)
        const isSoundCloudUrl = String(query).match(regexpConstants.soundCloud)
        const isSpotifyPlaylistUrl = String(query).match(regexpConstants.spotifyPlaylist)
        if(isYTUrl) {
            const searchResults = await search.YouTube(query)
            return searchResults
        }
        if(isSpotifyUrl) {
            const searchResults = await search.Spotify(query)
            return searchResults
        }
        if(isSoundCloudUrl) {
            const searchResults = await search.SoundCloud(query)
            return searchResults
        }
        if(isSpotifyPlaylistUrl) {
            const spotifyPlaylistData = await search.SpotifyPlaylist(query)
            return spotifyPlaylistData
        }
        if(!engine || engine === 'YouTube') {
            const searchResults = await search.YouTubeSearch(query)
            return searchResults
        }
        if(engine === 'SoundCloud') {
            const searchResults = await search.SoundCloudSearch(query)
            return searchResults
        }
    }
    /**
     * Get the existing Queue of a guild
     * @param {object} guild The guild of the queue you want to get
     * @returns {QueueBuilder} The queue of guild
     */
    async getQueue(guild) {
        const queue = this.queues[guild.id]
        return queue
    }
    /**
     * Check if the queue exists in a guild
     * @param {object} guild the guild you want to validate
     * @returns {boolean} true if the guild exists in the queue, false otherwise
     */
    existsQueue(guild) {
        const queue = this?.queues[guild.id]
        return queue ? true : false
    }
    /**
     * Create a new queue
     * @param {object} guild the guild you want to create a queue for
     * @param {object} options The options for creating a queue
     * @returns {QueueBuilder} The queue you just created
     */
    async createQueue(guild, options) {
        let queueFunctions = new QueueBuilder(guild, options)
        queueFunctions.on('EmitTrackStart', async (track) => {
            const queue = await this.getQueue(guild)
            this.emit('trackStart', queue, track)
        })

        queueFunctions.on('emitQueueEnded', async () => {
            const queue = await this.getQueue(guild)
            const connection = getVoiceConnection(guild.id) || undefined
            try {
                connection.destroy()
            } catch (error) {
                // no?
            }
            this.emit('queueEnded', queue)

            try {
                queueFunctions.removeAllListeners()
            } catch {
                try {
                    this.queues[guild.id].removeAllListeners()
                } catch (error) {
                    // no?
                }
            }

            delete this.queues.players[guild.id]
            delete this.queues[guild.id]
        })
        const queue = queueFunctions
        const player = queueFunctions.player
        this.queues[guild.id] = queue
        this.queues.players[guild.id] = player
        this.queues[guild.id].metadata = options?.metadata || undefined
        this.queues.players[guild.id] 
        this.queues[guild.id].kill = async () => {
            const connection = getVoiceConnection(guild.id) || undefined
            const status = this.queues.players[guild.id].state
            if(connection && status !== 'idle') {
                this.queues.players[guild.id].stop()
                connection.destroy()
            } else if(status === 'idle') {
                connection.destroy()
            } else if(connection) {
                connection.destroy()
            }

            delete this.queues[guild.id]
            delete this.queues.players[guild.id]
            queueFunctions = undefined
        }
        return this.queues[guild.id]
    }

    /**
     * Inject custom data to your tracks
     * @param {object} param target: "the target you want to inject", key: "the key that will be injected into the target", value: "the value that will be injected into the target"
     * @returns {any} injected target
     */
    injectCustomData({ target, key, value }) {
        if(!target || !key || !value) throw new Error("[ Dismusic Error ] Target, key or value cannot be undefined")
        target[key] = value
        return target
    }
}

module.exports = Player