Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

discord-player

Package Overview
Dependencies
Maintainers
1
Versions
359
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

discord-player - npm Package Compare versions

Comparing version 2.3.1 to 3.0.0-beta.0

.vscode/settings.json

2

index.js
module.exports = {
version: require('./package.json').version,
Player: require('./src/Player')
};
}
{
"name": "discord-player",
"version": "2.3.1",
"version": "3.0.0-beta.0",
"description": "Complete framework to facilitate music commands using discord.js v12",

@@ -33,5 +33,7 @@ "main": "index.js",

"merge-options": "^2.0.0",
"moment": "^2.27.0",
"node-fetch": "^2.6.0",
"soundcloud-scraper": "^2.0.0",
"spotify-url-info": "^1.3.1",
"ytpl": "^0.2.0",
"ytpl": "^0.2.4",
"ytsr": "^0.1.19"

@@ -38,0 +40,0 @@ },

@@ -49,2 +49,4 @@ # Discord Player

client.player = player;
// add the trackStart event so when a song will be played this message will be sent
client.player.on('trackStart', (message, track) => message.channel.send(`Now playing ${track.title}...`))

@@ -64,4 +66,4 @@ client.on("ready", () => {

if(command === "play"){
let track = await client.player.play(message.member.voice.channel, args[0], message.member.user.tag);
message.channel.send(`Currently playing ${track.name}! - Requested by ${track.requestedBy}`);
client.player.play(message, args[0], message.member.user);
// as we registered the event above, no need to send a success message here
}

@@ -82,26 +84,25 @@

#### Queue initialization
#### Play a track
* [play(voiceChannel, track, requestedBy)](https://discord-player.js.org/Player.html#play) - play a track in a server
* [play(message, track, requestedBy)](https://discord-player.js.org/Player.html#play) - play a track in a server
#### Queue management
#### Check if a track is being played
* [isPlaying(guildID)](https://discord-player.js.org/Player.html#isPlaying) - check if there is a queue for a specific server
* [isPlaying(message)](https://discord-player.js.org/Player.html#isPlaying) - check if there is a queue for a specific server
#### Manage tracks in your queue
#### Manage the queue
* [getQueue(guildID)](https://discord-player.js.org/Player.html#getQueue) - get the server queue
* [addToQueue(guildID, track, requestedBy)](https://discord-player.js.org/Player.html#addToQueue) - add a track to the server queue
* [clearQueue(guildID)](https://discord-player.js.org/Player.html#clearQueue) - clear the server queue
* [remove(guildID, track)](https://discord-player.js.org/Player.html#remove) - remove a track from the server queue
* [nowPlaying(guildID)](https://discord-player.js.org/Player.html#nowPlaying) - get the current track
* [getQueue(message)](https://discord-player.js.org/Player.html#getQueue) - get the server queue
* [clearQueue(message)](https://discord-player.js.org/Player.html#clearQueue) - clear the server queue
* [remove(message, track)](https://discord-player.js.org/Player.html#remove) - remove a track from the server queue
* [nowPlaying(message)](https://discord-player.js.org/Player.html#nowPlaying) - get the current track
#### Manage music stream
* [skip(guildID)](https://discord-player.js.org/Player.html#skip) - skip the current track
* [pause(guildID)](https://discord-player.js.org/Player.html#pause) - pause the current track
* [resume(guildID)](https://discord-player.js.org/Player.html#resume) - resume the current track
* [stop(guildID)](https://discord-player.js.org/Player.html#stop) - stop the current track
* [setFilters(guildID, newFilters)](https://discord-player.js.org/Player.html#setFilters) - update filters (bassboost for example)
* [setRepeatMode(guildID, boolean)](https://discord-player.js.org/Player.html#setRepeatMode) - enable or disable repeat mode for the server
* [skip(message)](https://discord-player.js.org/Player.html#skip) - skip the current track
* [pause(message)](https://discord-player.js.org/Player.html#pause) - pause the current track
* [resume(message)](https://discord-player.js.org/Player.html#resume) - resume the current track
* [stop(message)](https://discord-player.js.org/Player.html#stop) - stop the current track
* [setFilters(message, newFilters)](https://discord-player.js.org/Player.html#setFilters) - update filters (bassboost for example)
* [setRepeatMode(message, boolean)](https://discord-player.js.org/Player.html#setRepeatMode) - enable or disable repeat mode for the server

@@ -111,16 +112,47 @@ ### Event messages

```js
// Play the music
await client.player.play(message.member.voice.channel, "Despacito")
// Then add some messages that will be sent when the events will be triggered
client.player
// Then add some messages that will be sent when the events will be triggered
client.player.getQueue(message.guild.id)
.on('end', () => {
message.channel.send('There is no more music in the queue!');
// Send a message when a track starts
.on('trackStart', (message, track) => message.channel.send(`Now playing ${track.title}...`))
// Send a message when something is added to the queue
.on('trackAdd', (message, track) => message.channel.send(`${track.title} has been added to the queue!`))
.on('playlistAdd', (message, playlist) => message.channel.send(`${playlist.title} has been added to the queue (${playlist.items.length} songs)!`))
// Send messages to format search results
.on('searchResults', (message, query, tracks) => {
const embed = new Discord.MessageEmbed()
.setAuthor(`Here are your search results for ${query}!`)
.setDescription(tracks.map((t, i) => `${i}. ${t.title}`))
.setFooter('Send the number of the song you want to play!')
message.channel.send(embed);
})
.on('trackChanged', (oldTrack, newTrack) => {
message.channel.send(`Now playing ${newTrack.name}...`);
.on('searchInvalidResponse', (message, query, tracks, content, collector) => message.channel.send(`You must send a valid number between 1 and ${tracks.length}!`))
.on('searchCancel', (message, query, tracks) => message.channel.send('You did not provide a valid response... Please send the command again!'))
.on('noResults', (message, query) => message.channel.send(`No results found on YouTube for ${query}!`))
// Send a message when the music is stopped
.on('queueEnd', (message, queue) => message.channel.send('Music stopped as there is no more music in the queue!'))
.on('channelEmpty', (message, queue) => message.channel.send('Music stopped as there is no more member in the voice channel!'))
.on('botDisconnect', (message, queue) => message.channel.send('Music stopped as I have been disconnected from the channel!'))
// Error handling
.on('error', (message, error) => {
switch(error){
case 'NotPlaying':
message.channel.send('There is no music being played on this server!')
break;
case 'NotConnected':
message.channel.send('You are not connected in any voice channel!')
break;
case 'UnableToJoin':
message.channel.send('I am not able to join your voice channel, please check my permissions!')
break;
default:
message.channel.send(`Something went wrong... Error: ${error}`)
}
})
.on('channelEmpty', () => {
message.channel.send('Stop playing, there is no more member in the voice channel...');
});
```

@@ -132,3 +164,4 @@

* [AtlantaBot](https://github.com/Androz2091/AtlantaBot) by [me](https://github.com/Androz2091)
* [Discord-Music](https://github.com/hydraindia/discord-music) by [hydraindia](https://github.com/hydraindia)
* [Music-bot](https://github.com/ZerioDev/Music-bot) by [ZerioDev](https://github.com/ZerioDev)

@@ -6,4 +6,8 @@ const ytdl = require('discord-ytdl-core')

const spotify = require('spotify-url-info')
const soundcloud = require('soundcloud-scraper')
const moment = require('moment')
const Queue = require('./Queue')
const Track = require('./Track')
const Util = require('./Util')
const { EventEmitter } = require('events')

@@ -58,2 +62,3 @@ /**

* @property {boolean} [leaveOnEmpty=true] Whether the bot should leave the voice channel if there is no more member in it.
* @property {number} [leaveOnEmptyCooldown=0] Used when leaveOnEmpty is enabled, to let the time to users to come back in the voice channel.
*/

@@ -72,3 +77,3 @@

class Player {
class Player extends EventEmitter {
/**

@@ -80,4 +85,10 @@ * @param {Discord.Client} client Discord.js client

if (!client) throw new SyntaxError('Invalid Discord client')
super()
/**
* Utilities
* @type {Util}
*/
this.util = Util
/**
* Discord.js client instance

@@ -89,5 +100,5 @@ * @type {Discord.Client}

* Player queues
* @type {Queue[]}
* @type {Discord.Collection<Discord.Snowflake, Queue>}
*/
this.queues = []
this.queues = new Discord.Collection()
/**

@@ -112,33 +123,98 @@ * Player options

/**
* Set the filters enabled for the guild. [Full list of the filters](https://discord-player.js.org/global.html#Filters)
* @param {Discord.Snowflake} guildID
* @param {Filters} newFilters
*
* @ignore
* @param {String} query
*/
resolveQueryType (query) {
if (this.util.isSpotifyLink(query)) {
return 'spotify-song'
} else if (this.util.isYTPlaylistLink(query)) {
return 'youtube-playlist'
} else if (this.util.isYTVideoLink(query)) {
return 'youtube-video'
} else if (this.util.isSoundcloudLink(query)) {
return 'soundcloud-song'
} else {
return 'youtube-video-keywords'
}
}
/**
* Search tracks
* @ignore
* @param {Discord.Message} message
* @param {string} query
* @returns {Promise<Track>}
*/
_searchTracks (message, query) {
return new Promise(async (resolve) => {
const tracks = []
let updatedQuery = null
let queryType = this.resolveQueryType(query)
if (queryType === 'spotify-song') {
const matchSpotifyURL = query.match(/https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:track\/|\?uri=spotify:track:)((\w|-){22})/)
if (matchSpotifyURL) {
const spotifyData = await spotify.getPreview(query).catch(() => {})
if (spotifyData) {
updatedQuery = `${spotifyData.artist} - ${spotifyData.track}`
queryType = 'youtube-video-keywords'
}
}
} else if (queryType === 'soundcloud-song') {
const soundcloudData = await soundcloud.getSongInfo(query).catch(() => {})
if (soundcloudData) {
updatedQuery = `${soundcloudData.author.name} - ${soundcloudData.title}`
queryType = 'youtube-video-keywords'
}
}
if (queryType === 'youtube-video-keywords') {
await ytsr(updatedQuery || query).then((results) => {
if (results.items.length !== 0) {
const resultsVideo = results.items.filter((i) => i.type === 'video')
tracks.push(...resultsVideo.map((r) => new Track(r, message.author, null)))
}
}).catch(() => {})
}
if (tracks.length === 0) throw new Error('No tracks found for the specified query.')
this.emit('searchResults', message, query, tracks)
const collector = message.channel.createMessageCollector((m) => m.author.id === message.author.id, {
time: 60000,
errors: ['time']
})
collector.on('collect', ({ content }) => {
if (!isNaN(content) && parseInt(content) >= 1 && parseInt(content) <= tracks.length) {
const index = parseInt(content, 10)
const track = tracks[index - 1]
collector.stop()
resolve(track)
} else {
this.emit('searchInvalidResponse', message, query, tracks, content, collector)
}
})
collector.on('end', (collected, reason) => {
if (reason === 'time') {
this.emit('searchCancel', message, query, tracks)
}
})
})
}
/**
* Change the filters.
* @param {Discord.Message} message
* @param {Partial<Filters>} newFilters The filters to update and their new status.
* @example
* client.on('message', async (message) => {
*
* const args = message.content.slice(settings.prefix.length).trim().split(/ +/g);
* const command = args.shift().toLowerCase();
*
* if(command === 'bassboost'){
* const bassboostEnabled = client.player.getQueue(message.guild.id).filters.bassboost;
* if(!bassboostEnabled){
* client.player.setFilters(message.guild.id, {
* bassboost: true
* });
* message.channel.send("Bassboost effect has been enabled!");
* } else {
* client.player.setFilters(message.guild.id, {
* bassboost: false
* });
* message.channel.send("Bassboost effect has been disabled!");
* }
* }
*
* client.player.setFilters(message, {
* bassboost: true
* });
*/
setFilters (guildID, newFilters) {
setFilters (message, newFilters) {
return new Promise((resolve, reject) => {
// Get guild queue
const queue = this.queues.find((g) => g.guildID === guildID)
const queue = this.queues.find((g) => g.guildID === message.guild.id)
if (!queue) return reject(new Error('Not playing'))

@@ -148,3 +224,3 @@ Object.keys(newFilters).forEach((filterName) => {

})
this._playYTDLStream(queue, true, false)
this._playYTDLStream(queue, true)
})

@@ -154,67 +230,61 @@ }

/**
* Resolve an array of tracks objects from a query string
* @param {string} query The query
* @param {boolean} allResults Whether all the results should be returned, or only the first one
* @returns {Promise<Track[]>}
*
* @example
* client.on('message', async (message) => {
*
* const args = message.content.slice(settings.prefix.length).trim().split(/ +/g);
* const command = args.shift().toLowerCase();
*
* if(command === 'play'){
* // Search for tracks
* let tracks = await client.player.searchTracks(args[0]);
* // Sends an embed with the 10 first songs
* if(tracks.length > 10) tracks = tracks.substr(0, 10);
* const embed = new Discord.MessageEmbed()
* .setDescription(tracks.map((t, i) => `**${i+1} -** ${t.name}`).join("\n"))
* .setFooter("Send the number of the track you want to play!");
* message.channel.send(embed);
* // Wait for user answer
* await message.channel.awaitMessages((m) => m.content > 0 && m.content < 10, { max: 1, time: 20000, errors: ["time"] }).then(async (answers) => {
* let index = parseInt(answers.first().content, 10);
* track = track[index-1];
* // Then play the song
* client.player.play(message.member.voice.channel, track);
* });
* }
*
* });
* Check whether there is a music played in the server
* @param {Discord.Message} message
*/
searchTracks (query, allResults = false) {
return new Promise(async (resolve, reject) => {
if (ytpl.validateURL(query)) {
const playlistID = await ytpl.getPlaylistID(query).catch(() => {})
if (playlistID) {
const playlist = await ytpl(playlistID).catch(() => {})
if (playlist) {
return resolve(playlist.items.map((i) => new Track({
title: i.title,
duration: i.duration,
thumbnail: i.thumbnail,
author: i.author,
link: i.url,
fromPlaylist: true
}, null, null)))
}
}
}
const matchSpotifyURL = query.match(/https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:track\/|\?uri=spotify:track:)((\w|-){22})/)
if (matchSpotifyURL) {
const spotifyData = await spotify.getPreview(query).catch(e => resolve([]))
query = `${spotifyData.artist} - ${spotifyData.track}`
}
// eslint-disable-next-line no-useless-escape
const matchYoutubeURL = query.match(/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/)
if (matchYoutubeURL) {
query = matchYoutubeURL[1]
}
ytsr(query).then((results) => {
if (results.items.length < 1) return resolve([])
const resultsVideo = results.items.filter((i) => i.type === 'video')
resolve(allResults ? resultsVideo.map((r) => new Track(r, null, null)) : [new Track(resultsVideo[0], null, null)])
}).catch(() => {
return resolve([])
isPlaying (message) {
return this.queues.some((g) => g.guildID === message.guild.id)
}
/**
* Add a track to the queue
* @ignore
* @param {Discord.Message} message
* @param {Track} track
* @returns {Queue}
*/
_addTrackToQueue (message, track) {
const queue = this.getQueue(message)
if (!queue) throw new Error('NotPlaying')
if (!track || !(track instanceof Track)) throw new Error('No track to add to the queue specified')
queue.tracks.push(track)
return queue
}
/**
* Add multiple tracks to the queue
* @ignore
* @param {Discord.Message} message
* @param {Track[]} tracks
* @returns {Queue}
*/
_addTracksToQueue (message, tracks) {
const queue = this.getQueue(message)
if (!queue) throw new Error('Cannot add tracks to queue because no song is currently played on the server.')
queue.tracks.push(...tracks)
return queue
}
/**
* Create a new queue and play the first track
* @ignore
* @param {Discord.Message} message
* @param {Track} track
* @returns {Promise<Queue>}
*/
_createQueue (message, track) {
return new Promise((resolve, reject) => {
const channel = message.member.voice ? message.member.voice.channel : null
if (!channel) reject(new Error('NotConnected'))
const queue = new Queue(message.guild.id, message, this.filters)
this.queues.set(message.guild.id, queue)
channel.join().then((connection) => {
queue.voiceConnection = connection
queue.tracks.push(track)
this.emit('queueCreate', message, queue)
resolve(queue)
this._playTrack(queue, true)
}).catch((err) => {
console.error(err)
this.queues.delete(message.guild.id)
reject(new Error('UnableToJoin'))
})

@@ -225,108 +295,68 @@ })

/**
* Whether a guild is currently playing something
* @param {Discord.Snowflake} guildID The guild ID to check
* @returns {boolean} Whether the guild is currently playing tracks
* Handle playlist by fetching the tracks and adding them to the queue
* @ignore
* @param {Discord.Message} message
* @param {String} query
*/
isPlaying (guildID) {
return this.queues.some((g) => g.guildID === guildID)
async _handlePlaylist (message, query) {
const playlist = await ytpl(query).catch(() => {})
if (!playlist) return this.emit('noResults', message, query)
playlist.tracks = playlist.items.map((item) => new Track(item, message.author))
playlist.duration = playlist.tracks.reduce((prev, next) => prev + next.duration, 0)
playlist.thumbnail = playlist.tracks[0].thumbnail
playlist.requestedBy = message.author
if (this.isPlaying(message)) {
const queue = this._addTracksToQueue(message, playlist.tracks)
this.emit('playlistAdd', message, queue, playlist)
} else {
const track = new Track(playlist.tracks.shift(), message.author)
const queue = await this._createQueue(message, track).catch((e) => this.emit('error', message, e))
this._addTracksToQueue(message, playlist.tracks)
}
}
/**
* Play a track in a voice channel
* @param {Discord.VoiceChannel} voiceChannel The voice channel in which the track will be played
* @param {Track|string} track The name of the track to play
* @param {Discord.User?} user The user who requested the track
* @returns {any} The played content
* Play a track in the server. Supported query types are `keywords`, YouTube video links`, `YouTube playlists links`, Spotify track link` or `SoundCloud song link`.
* @param {Discord.Message} message
* @param {String} query
* @returns {Promise<void>}
*
* @example
* client.on('message', async (message) => {
*
* const args = message.content.slice(settings.prefix.length).trim().split(/ +/g);
* const command = args.shift().toLowerCase();
*
* // !play Despacito
* // will play "Despacito" in the member voice channel
*
* if(command === 'play'){
* const result = await client.player.play(message.member.voice.channel, args.join(" "));
* if(result.type === 'playlist'){
* message.channel.send(`${result.tracks.length} songs added to the queue!\nCurrently playing **${result.tracks[0].name}**...`);
* } else {
* message.channel.send(`Currently playing ${result.name}...`);
* }
* }
*
* });
* client.player.play(message, "Despacito");
*/
play (voiceChannel, track, user) {
this.queues = this.queues.filter((g) => g.guildID !== voiceChannel.id)
return new Promise(async (resolve, reject) => {
if (!voiceChannel || typeof voiceChannel !== 'object') {
return reject(new Error(`voiceChannel must be type of VoiceChannel. value=${voiceChannel}`))
async play (message, query) {
const isPlaying = this.isPlaying(message)
if (this.util.isYTPlaylistLink(query)) {
return this._handlePlaylist(message, query)
}
let trackToPlay
if (query instanceof Track) {
trackToPlay = query
} else if (this.util.isYTVideoLink(query)) {
const videoData = await ytdl.getBasicInfo(query)
trackToPlay = new Track(videoData, message.author)
} else {
trackToPlay = await this._searchTracks(message, query)
}
if (trackToPlay) {
if (this.isPlaying(message)) {
const queue = this._addTrackToQueue(message, trackToPlay)
this.emit('trackAdd', message, queue, queue.tracks[queue.tracks.length - 1])
} else {
const queue = await this._createQueue(message, trackToPlay)
this.emit('trackStart', message, queue.tracks[0])
}
const connection = voiceChannel.client.voice.connections.find((c) => c.channel.id === voiceChannel.id) || await voiceChannel.join()
// Create a new guild with data
const queue = new Queue(voiceChannel.guild.id)
queue.voiceConnection = connection
queue.filters = {}
Object.keys(this.filters).forEach((f) => {
queue.filters[f] = false
})
let result = null
if (typeof track === 'object') {
track.requestedBy = user
result = track
// Add the track to the queue
queue.tracks.push(track)
} else if (typeof track === 'string') {
const results = await this.searchTracks(track).catch(() => {
return reject(new Error('Not found'))
})
if (!results) return
if (results.length > 1) {
result = {
type: 'playlist',
tracks: results
}
} else if (results[0]) {
result = results[0]
} else {
return reject(new Error('Not found'))
}
results.forEach((i) => {
i.requestedBy = user
queue.tracks.push(i)
})
}
// Add the queue to the list
this.queues.push(queue)
// Play the track
this._playTrack(queue.guildID, true)
// Resolve the track
resolve(result)
})
}
}
/**
* Pause the current track
* @param {Discord.Snowflake} guildID The ID of the guild where the current track should be paused
* @returns {Promise<Track>} The paused track
*
* Pause the music in the server.
* @param {Discord.Message} message
* @example
* client.on('message', async (message) => {
*
* const args = message.content.slice(settings.prefix.length).trim().split(/ +/g);
* const command = args.shift().toLowerCase();
*
* if(command === 'pause'){
* const track = await client.player.pause(message.guild.id);
* message.channel.send(`${track.name} paused!`);
* }
*
* });
* client.player.pause(message);
*/
pause (guildID) {
pause (message) {
return new Promise((resolve, reject) => {
// Get guild queue
const queue = this.queues.find((g) => g.guildID === guildID)
const queue = this.queues.find((g) => g.guildID === message.guild.id)
if (!queue) return reject(new Error('Not playing'))

@@ -342,127 +372,65 @@ // Pause the dispatcher

/**
* Resume the current track
* @param {Discord.Snowflake} guildID The ID of the guild where the current track should be resumed
* @returns {Promise<Track>} The resumed track
*
* Resume the music in the server.
* @param {Discord.Message} message
* @returns {Queue}
* @example
* client.on('message', async (message) => {
*
* const args = message.content.slice(settings.prefix.length).trim().split(/ +/g);
* const command = args.shift().toLowerCase();
*
* if(command === 'resume'){
* const track = await client.player.resume(message.guild.id);
* message.channel.send(`${track.name} resumed!`);
* }
*
* });
* client.player.resume(message);
*/
resume (guildID) {
return new Promise((resolve, reject) => {
// Get guild queue
const queue = this.queues.find((g) => g.guildID === guildID)
if (!queue) return reject(new Error('Not playing'))
// Pause the dispatcher
queue.voiceConnection.dispatcher.resume()
queue.paused = false
// Resolve the guild queue
resolve(queue.playing)
})
resume (message) {
// Get guild queue
const queue = this.queues.find((g) => g.guildID === message.guild.id)
if (!queue) return this.emit('error', message, 'NotPlaying')
// Pause the dispatcher
queue.voiceConnection.dispatcher.resume()
queue.paused = false
// Resolve the guild queue
return queue
}
/**
* Stop the music in the guild
* @param {Discord.Snowflake} guildID The ID of the guild where the music should be stopped
* @returns {Promise<void>}
*
* Stop the music in the server.
* @param {Discord.Message} message
* @example
* client.on('message', (message) => {
*
* const args = message.content.slice(settings.prefix.length).trim().split(/ +/g);
* const command = args.shift().toLowerCase();
*
* if(command === 'stop'){
* client.player.stop(message.guild.id);
* message.channel.send('Music stopped!');
* }
*
* });
* client.player.stop(message);
*/
stop (guildID) {
return new Promise((resolve, reject) => {
// Get guild queue
const queue = this.queues.find((g) => g.guildID === guildID)
if (!queue) return reject(new Error('Not playing'))
// Stop the dispatcher
queue.stopped = true
queue.tracks = []
if (queue.stream) queue.stream.destroy()
queue.voiceConnection.dispatcher.end()
// Resolve
resolve()
})
stop (message) {
// Get guild queue
const queue = this.queues.find((g) => g.guildID === message.guild.id)
if (!queue) return this.emit('error', message, 'NotPlaying')
// Stop the dispatcher
queue.stopped = true
queue.tracks = []
if (queue.stream) queue.stream.destroy()
queue.voiceConnection.dispatcher.end()
if (this.options.leaveOnStop) queue.voiceConnection.channel.leave()
this.queues.delete(message.guild.id)
}
/**
* Update the volume
* @param {Discord.Snowflake} guildID The ID of the guild where the music should be modified
* @param {number} percent The new volume (0-100)
* @returns {Promise<void>}
*
* Change the server volume.
* @param {Discord.Message} message
* @param {number} percent
* @returns {Queue}
* @example
* client.on('message', (message) => {
*
* const args = message.content.slice(settings.prefix.length).trim().split(/ +/g);
* const command = args.shift().toLowerCase();
*
* if(command === 'set-volume'){
* client.player.setVolume(message.guild.id, parseInt(args[0]));
* message.channel.send(`Volume set to ${args[0]} !`);
* }
*
* });
* client.player.setVolume(message, 90);
*/
setVolume (guildID, percent) {
return new Promise((resolve, reject) => {
// Get guild queue
const queue = this.queues.find((g) => g.guildID === guildID)
if (!queue) return reject(new Error('Not playing'))
// Update volume
queue.volume = percent
queue.voiceConnection.dispatcher.setVolumeLogarithmic(queue.calculatedVolume / 200)
// Resolve guild queue
resolve()
})
setVolume (message, percent) {
// Get guild queue
const queue = this.queues.get(message.guild.id)
if (!queue) return this.emit('error', message, 'NotPlaying')
// Update volume
queue.volume = percent
queue.voiceConnection.dispatcher.setVolumeLogarithmic(queue.calculatedVolume / 200)
// Return the queue
return queue
}
/**
* Get a guild queue
* @param {Discord.Snowflake} guildID
* @returns {?Queue}
*
* @example
* client.on('message', (message) => {
*
* const args = message.content.slice(settings.prefix.length).trim().split(/ +/g);
* const command = args.shift().toLowerCase();
*
* if(command === 'queue'){
* const queue = await client.player.getQueue(message.guild.id);
* message.channel.send('Server queue:\n'+(queue.tracks.map((track, i) => {
* return `${i === 0 ? 'Current' : `#${i+1}`} - ${track.name} | ${track.author}`;
* }).join('\n')));
* }
*
* // Output:
*
* // Server queue:
* // Current - Despacito | Luis Fonsi
* // #2 - Memories | Maroon 5
* // #3 - Dance Monkey | Tones And I
* // #4 - Circles | Post Malone
* });
* Get the server queue.
* @param {Discord.Message} message
* @returns {Queue}
*/
getQueue (guildID) {
getQueue (message) {
// Gets guild queue
const queue = this.queues.find((g) => g.guildID === guildID)
const queue = this.queues.get(message.guild.id)
return queue

@@ -472,305 +440,119 @@ }

/**
* Add a track to the guild queue
* @param {Discord.Snowflake} guildID The ID of the guild where the track should be added
* @param {Track|string} trackName The name of the track to add to the queue
* @param {Discord.User?} user The user who requested the track
* @returns {any} The content added to the queue
*
* @example
* client.on('message', async (message) => {
*
* const args = message.content.slice(settings.prefix.length).trim().split(/ +/g);
* const command = args.shift().toLowerCase();
*
* if(command === 'play'){
* let trackPlaying = client.player.isPlaying(message.guild.id);
* // If there's already a track being played
* if(trackPlaying){
* const result = await client.player.addToQueue(message.guild.id, args.join(" "));
* if(result.type === 'playlist'){
* message.channel.send(`${result.tracks.length} songs added to the queue!`);
* } else {
* message.channel.send(`${result.name} added to the queue!`);
* }
* } else {
* // Else, play the track
* const result = await client.player.addToQueue(message.member.voice.channel, args[0]);
* if(result.type === 'playlist'){
* message.channel.send(`${result.tracks.length} songs added to the queue\nCurrently playing **${result.tracks[0].name}**!`);
* } else {
* message.channel.send(`Currently playing ${result.name}`);
* }
* }
* }
*
* });
* Clears the server queue.
* @param {Discord.Message} message
* @returns {Queue}
*/
addToQueue (guildID, track, user) {
return new Promise(async (resolve, reject) => {
// Get guild queue
const queue = this.queues.find((g) => g.guildID === guildID)
if (!queue) return reject(new Error('Not playing'))
// Search the track
let result = null
if (typeof track === 'object') {
track.requestedBy = user
result = track
// Add the track to the queue
queue.tracks.push(track)
} else if (typeof track === 'string') {
const results = await this.searchTracks(track).catch(() => {
return reject(new Error('Not found'))
})
if (!results) return
if (results.length > 1) {
result = {
type: 'playlist',
tracks: results
}
} else if (results[0]) {
result = results[0]
} else {
return reject(new Error('Not found'))
}
results.forEach((i) => {
i.requestedBy = user
queue.tracks.push(i)
})
}
// Resolve the result
resolve(result)
})
clearQueue (message) {
// Get guild queue
const queue = this.queues.get(message.guild.id)
if (!queue) return this.emit('error', message, 'NotPlaying')
// Clear queue
queue.tracks = []
// Return the queue
return queue
}
/**
* Clear the guild queue, except the current track
* @param {Discord.Snowflake} guildID The ID of the guild where the queue should be cleared
* @returns {Promise<Queue>} The updated queue
*
* @example
* client.on('message', (message) => {
*
* const args = message.content.slice(settings.prefix.length).trim().split(/ +/g);
* const command = args.shift().toLowerCase();
*
* if(command === 'clear-queue'){
* client.player.clearQueue(message.guild.id);
* message.channel.send('Queue cleared!');
* }
*
* });
* Skips to the next song.
* @param {Discord.Message} message
* @returns {Queue}
*/
clearQueue (guildID) {
return new Promise((resolve, reject) => {
// Get guild queue
const queue = this.queues.find((g) => g.guildID === guildID)
if (!queue) return reject(new Error('Not playing'))
// Clear queue
queue.tracks = []
// Resolve guild queue
resolve(queue)
})
skip (message) {
// Get guild queue
const queue = this.queues.get(message.guild.id)
if (!queue) return this.emit('error', message, 'NotPlaying')
const currentTrack = queue.playing
// End the dispatcher
queue.voiceConnection.dispatcher.end()
queue.lastSkipped = true
// Return the queue
return queue
}
/**
* Skip a track
* @param {Discord.Snowflake} guildID The ID of the guild where the track should be skipped
* @returns {Promise<Track>}
*
* @example
* client.on('message', async (message) => {
*
* const args = message.content.slice(settings.prefix.length).trim().split(/ +/g);
* const command = args.shift().toLowerCase();
*
* if(command === 'skip'){
* const track = await client.player.skip(message.guild.id);
* message.channel.send(`${track.name} skipped!`);
* }
*
* });
* Get the played song in the server.
* @param {Discord.Message} message
* @returns {Track}
*/
skip (guildID) {
return new Promise((resolve, reject) => {
// Get guild queue
const queue = this.queues.find((g) => g.guildID === guildID)
if (!queue) return reject(new Error('Not playing'))
const currentTrack = queue.playing
// End the dispatcher
queue.voiceConnection.dispatcher.end()
queue.lastSkipped = true
// Resolve the current track
resolve(currentTrack)
})
nowPlaying (message) {
// Get guild queue
const queue = this.queues.get(message.guild.id)
if (!queue) return this.emit('error', message, 'NotPlaying')
const currentTrack = queue.tracks[0]
// Return the current track
return currentTrack
}
/**
* Get the currently playing track
* @param {Discord.Snowflake} guildID
* @returns {Promise<Track>} The track which is currently played
*
* @example
* client.on('message', async (message) => {
*
* const args = message.content.slice(settings.prefix.length).trim().split(/ +/g);
* const command = args.shift().toLowerCase();
*
* if(command === 'now-playing'){
* let track = await client.player.nowPlaying(message.guild.id);
* message.channel.send(`Currently playing ${track.name}...`);
* }
*
* });
* Enable or disable repeat mode in the server.
* @param {Discord.Message} message
* @param {boolean} enabled
* @returns {boolean} whether the repeat mode is now enabled.
*/
nowPlaying (guildID) {
return new Promise((resolve, reject) => {
// Get guild queue
const queue = this.queues.find((g) => g.guildID === guildID)
if (!queue) return reject(new Error('Not playing'))
const currentTrack = queue.playing
// Resolve the current track
resolve(currentTrack)
})
setRepeatMode (message, enabled) {
// Get guild queue
const queue = this.queues.get(message.guild.id)
if (!queue) return this.emit('error', message, 'NotPlaying')
// Enable/Disable repeat mode
queue.repeatMode = enabled
// Return the repeat mode
return queue.repeatMode
}
/**
* Enable or disable the repeat mode
* @param {Discord.Snowflake} guildID
* @param {Boolean} enabled Whether the repeat mode should be enabled
* @returns {Promise<Void>}
*
* @example
* client.on('message', async (message) => {
*
* const args = message.content.slice(settings.prefix.length).trim().split(/ +/g);
* const command = args.shift().toLowerCase();
*
* if(command === 'repeat-mode'){
* const repeatModeEnabled = client.player.getQueue(message.guild.id).repeatMode;
* if(repeatModeEnabled){
* // if the repeat mode is currently enabled, disable it
* client.player.setRepeatMode(message.guild.id, false);
* message.channel.send("Repeat mode disabled! The current song will no longer be played again and again...");
* } else {
* // if the repeat mode is currently disabled, enable it
* client.player.setRepeatMode(message.guild.id, true);
* message.channel.send("Repeat mode enabled! The current song will be played again and again until you run the command again!");
* }
* }
*
* });
* Shuffle the queue of the server.
* @param {Discord.Message} message
* @returns {}
*/
setRepeatMode (guildID, enabled) {
return new Promise((resolve, reject) => {
// Get guild queue
const queue = this.queues.find((g) => g.guildID === guildID)
if (!queue) return reject(new Error('Not playing'))
// Enable/Disable repeat mode
queue.repeatMode = enabled
// Resolve
resolve()
})
shuffle (message) {
// Get guild queue
const queue = this.queues.get(message.guild.id)
if (!queue) return this.emit('error', message, 'NotPlaying')
// Shuffle the queue (except the first track)
const currentTrack = queue.tracks.shift()
queue.tracks = queue.tracks.sort(() => Math.random() - 0.5)
queue.tracks.unshift(currentTrack)
// Return the queue
return queue
}
/**
* Shuffle the guild queue (except the first track)
* @param {Discord.Snowflake} guildID The ID of the guild where the queue should be shuffled
* @returns {Promise<Queue>} The updated queue
*
* @example
* client.on('message', async (message) => {
*
* const args = message.content.slice(settings.prefix.length).trim().split(/ +/g);
* const command = args.shift().toLowerCase();
*
* if(command === 'shuffle'){
* // Shuffle the server queue
* client.player.shuffle(message.guild.id).then(() => {
* message.channel.send('Queue shuffled!');
* });
* }
*
* });
* Remove a track from the queue of the server
* @param {Discord.Message} message
* @param {Track|number} track
* @returns {Track} the removed track
*/
shuffle (guildID) {
return new Promise((resolve, reject) => {
// Get guild queue
const queue = this.queues.find((g) => g.guildID === guildID)
if (!queue) return reject(new Error('Not playing'))
// Shuffle the queue (except the first track)
const currentTrack = queue.tracks.shift()
queue.tracks = queue.tracks.sort(() => Math.random() - 0.5)
queue.tracks.unshift(currentTrack)
// Resolve
resolve(queue)
})
}
/**
* Remove a track from the queue
* @param {Discord.Snowflake} guildID The ID of the guild where the track should be removed
* @param {number|Track} track The index of the track to remove or the track to remove object
* @returns {Promise<Track|null>}
*
* @example
* client.on('message', async (message) => {
*
* const args = message.content.slice(settings.prefix.length).trim().split(/ +/g);
* const command = args.shift().toLowerCase();
*
* if(command === 'remove'){
* // Remove a track from the queue
* client.player.remove(message.guild.id, args[0]).then(() => {
* message.channel.send('Removed track!');
* });
* }
*
* });
*/
remove (guildID, track) {
return new Promise((resolve, reject) => {
// Gets guild queue
const queue = this.queues.find((g) => g.guildID === guildID)
if (!queue) return reject(new Error('Not playing'))
// Remove the track from the queue
let trackFound = null
if (typeof track === 'number') {
trackFound = queue.tracks[track]
if (trackFound) {
queue.tracks = queue.tracks.filter((t) => t !== trackFound)
}
} else {
trackFound = queue.tracks.find((s) => s === track)
if (trackFound) {
queue.tracks = queue.tracks.filter((s) => s !== trackFound)
}
remove (message, track) {
// Get guild queue
const queue = this.queues.get(message.guild.id)
if (!queue) return this.emit('error', message, 'NotPlaying')
// Remove the track from the queue
let trackFound = null
if (typeof track === 'number') {
trackFound = queue.tracks[track]
if (trackFound) {
queue.tracks = queue.tracks.filter((t) => t !== trackFound)
}
// Resolve
resolve(trackFound)
})
} else {
trackFound = queue.tracks.find((s) => s === track)
if (trackFound) {
queue.tracks = queue.tracks.filter((s) => s !== trackFound)
}
}
// Resolve
return trackFound
}
/**
* Creates progress bar of the current song
* @param {Discord.Snowflake} guildID
* @returns {String}
*
* @example
* client.on('message', async (message) => {
*
* const args = message.content.slice(settings.prefix.length).trim().split(/ +/g);
* const command = args.shift().toLowerCase();
*
* if(command === 'now-playing'){
* client.player.nowPlaying(message.guild.id).then((song) => {
* message.channel.send('Currently playing ' + song.name + '\n\n'+ client.player.createProgressBar(message.guild.id));
* });
* }
*
* });
* Create a progress bar for the queue of the server.
* @param {Discord.Message} message
* @param {Object} options
* @param {boolean} options.timecodes
* @returns {string}
*/
createProgressBar (guildID) {
createProgressBar (message, options) {
// Gets guild queue
const queue = this.queues.find((g) => g.guildID === guildID)
const queue = this.queues.get(message.guild.id)
if (!queue) return
const timecodes = options && typeof options === 'object' ? options.timecodes : false
// Stream time of the dispatcher

@@ -788,5 +570,15 @@ const currentStreamTime = queue.voiceConnection.dispatcher

bar.splice(index, 0, '🔘')
return bar.join('')
if (timecodes) {
const currentTimecode = (currentStreamTime >= 3600000 ? moment(currentStreamTime).format('H:mm:ss') : moment(currentStreamTime).format('m:ss'))
return `${currentTimecode} ┃ ${bar.join('')} ┃ ${queue.playing.duration}`
} else {
return `${bar.join('')}`
}
} else {
return '🔘▬▬▬▬▬▬▬▬▬▬▬▬▬▬'
if (timecodes) {
const currentTimecode = (currentStreamTime >= 3600000 ? moment(currentStreamTime).format('H:mm:ss') : moment(currentStreamTime).format('m:ss'))
return `${currentTimecode} ┃ 🔘▬▬▬▬▬▬▬▬▬▬▬▬▬▬ ┃ ${queue.playing.duration}`
} else {
return '🔘▬▬▬▬▬▬▬▬▬▬▬▬▬▬'
}
}

@@ -796,5 +588,3 @@ }

/**
* Handle the voice state update event
* @ignore
* @private
* Handle voiceStateUpdate event.
* @param {Discord.VoiceState} oldState

@@ -804,27 +594,32 @@ * @param {Discord.VoiceState} newState

_handleVoiceStateUpdate (oldState, newState) {
// Search for a queue for this channel
const queue = this.queues.find((g) => g.guildID === oldState.guild.id)
if (!queue) return
// if the bot has been kicked from the channel, destroy ytdl stream and remove the queue
if (newState.member.id === this.client.user.id && !newState.channelID) {
queue.stream.destroy()
this.queues.delete(newState.guild.id)
this.emit('botDisconnect', queue.firstMessage)
}
// process leaveOnEmpty checks
if (!this.options.leaveOnEmpty) return
// If the member leaves a voice channel
if (!oldState.channelID || newState.channelID) return
// Search for a queue for this channel
const queue = this.queues.find((g) => g.voiceConnection.channel.id === oldState.channelID)
if (queue) {
// If the channel is not empty
if (queue.voiceConnection.channel.members.size > 1) return
// If the channel is not empty
if (!this.util.isVoiceEmpty(queue.voiceConnection.channel)) return
setTimeout(() => {
if (!this.util.isVoiceEmpty(queue.voiceConnection.channel)) return
if (!this.queues.has(queue.guildID)) return
// Disconnect from the voice channel
queue.voiceConnection.channel.leave()
// Delete the queue
this.queues = this.queues.filter((g) => g.guildID !== queue.guildID)
this.queues.delete(queue.guildID)
// Emit end event
queue.emit('channelEmpty')
}
queue.emit('channelEmpty', queue.firstMessage, queue)
}, this.options.leaveOnEmptyCooldown ?? 0)
}
/**
* Play a stream in a channel
* @ignore
* @private
* @param {Queue} queue The queue to play
* @param {Boolean} updateFilter Whether this method is called to update some ffmpeg filters
* @returns {Promise<void>}
*/
_playYTDLStream (queue, updateFilter) {

@@ -872,3 +667,3 @@ return new Promise((resolve) => {

// Play the next track
return this._playTrack(queue.guildID, false)
return this._playTrack(queue, false)
})

@@ -880,34 +675,31 @@ }, 1000)

/**
* Start playing a track in a guild
* @ignore
* @private
* @param {Discord.Snowflake} guildID
* @param {Boolean} firstPlay Whether the function was called from the play() one
*
* @param {Queue} queue The queue to play.
* @param {*} firstPlay
*/
async _playTrack (guildID, firstPlay) {
// Get guild queue
const queue = this.queues.find((g) => g.guildID === guildID)
// If there isn't any music in the queue
if (queue.tracks.length < 1 && !firstPlay && !queue.repeatMode) {
async _playTrack (queue, firstPlay) {
if (this.options.leaveOnEmpty && this.util.isVoiceEmpty(queue.voiceConnection.channel)) {
}
if (queue.stopped) return
// If there isn't next music in the queue
if (queue.tracks.length === 1 && !queue.repeatMode && !firstPlay) {
// Leave the voice channel
if (this.options.leaveOnEnd && !queue.stopped) queue.voiceConnection.channel.leave()
// Remove the guild from the guilds list
this.queues = this.queues.filter((g) => g.guildID !== guildID)
this.queues.delete(queue.guildID)
// Emit stop event
if (queue.stopped) {
if (this.options.leaveOnStop) queue.voiceConnection.channel.leave()
return queue.emit('stop')
return queue.emit('musicStop')
}
// Emit end event
return queue.emit('end')
return queue.emit('queueEnd', queue.firstMessage, queue)
}
const wasPlaying = queue.playing
const nowPlaying = queue.playing = queue.repeatMode ? wasPlaying : queue.tracks.shift()
// if the track needs to be the next one
if (!queue.repeatMode && !firstPlay) queue.tracks.shift()
const track = queue.playing
// Reset lastSkipped state
queue.lastSkipped = false
this._playYTDLStream(queue, false).then(() => {
// Emit trackChanged event
if (!firstPlay) {
queue.emit('trackChanged', wasPlaying, nowPlaying, queue.lastSkipped, queue.repeatMode)
}
if (!firstPlay) this.emit('trackStart', queue.firstMessage, track, queue)
})

@@ -918,1 +710,94 @@ }

module.exports = Player
/**
* Emitted when a track starts
* @event Player#trackStart
* @param {Discord.Message} message
* @param {Queue} queue
* @param {Track} track
*/
/**
* Emitted when a playlist is started
* @event Player#queueCreate
* @param {Discord.Message} message
* @param {Queue} queue
* @param {Object} playlist
* @param {Track} track
*/
/**
* Emitted when the bot is awaiting search results
* @event Player#searchResults
* @param {Discord.Message} message
* @param {string} query
* @param {Track[]} tracks
*/
/**
* Emitted when the user has sent an invalid response for search results
* @event Player#searchInvalidResponse
* @param {Discord.Message} message
* @param {string} query
* @param {Track[]} tracks
* @param {string} invalidResponse
* @param {Discord.MessageCollector} collector
*/
/**
* Emitted when the bot has stopped awaiting search results (timeout)
* @event Player#searchCancel
* @param {Discord.Message} message
* @param {string} query
* @param {Track[]} tracks
*/
/**
* Emitted when the bot can't find related results to the query
* @event Player#noResults
* @param {Discord.Message} message
* @param {string} query
*/
/**
* Emitted when the bot is disconnected from the channel
* @event Player#botDisconnect
* @param {Discord.Message} message
*/
/**
* Emitted when the channel of the bot is empty
* @event Player#channelEmpty
* @param {Discord.Message} message
* @param {Queue} queue
*/
/**
* Emitted when the queue of the server is ended
* @event Player#queueEnd
* @param {Discord.Message} message
* @param {Queue} queue
*/
/**
* Emitted when a track is added to the queue
* @event Player#trackAdd
* @param {Discord.Message} message
* @param {Queue} queue
* @param {Track} track
*/
/**
* Emitted when a playlist is added to the queue
* @event Player#playlistAdd
* @param {Discord.Message} message
* @param {Queue} queue
* @param {Object} playlist
*/
/**
* Emitted when an error is triggered
* @event Player#error
* @param {Discord.Message} message
* @param {string} error It can be `NotConnected`, `UnableToJoin` or `NotPlaying`.
*/
const Discord = require('discord.js')
const { EventEmitter } = require('events')
const Track = require('./Track')
const Player = require('./Player')
const { Stream } = require('stream')

@@ -11,4 +13,6 @@ /**

* @param {Discord.Snowflake} guildID ID of the guild this queue is for.
* @param {Discord.Message} message Message that initialized the queue
* @param {import('./Player').Filters[]} filters Filters the queue should be initialized with.
*/
constructor (guildID) {
constructor (guildID, message, filters) {
super()

@@ -26,6 +30,6 @@ /**

/**
* The song currently played.
* @type {Track}
* The ytdl stream.
* @type {any}
*/
this.playing = null
this.stream = null
/**

@@ -66,2 +70,5 @@ * The tracks of this queue. The first one is currenlty playing and the others are going to be played.

this.filters = {}
Object.keys(filters).forEach((f) => {
this.filters[f] = false
})
/**

@@ -72,4 +79,13 @@ * Additional stream time

this.additionalStreamTime = 0
/**
* Message that initialized the queue
* @type {Discord.Message}
*/
this.firstMessage = message
}
get playing () {
return this.tracks[0]
}
get calculatedVolume () {

@@ -81,59 +97,1 @@ return this.filters.bassboost ? this.volume + 50 : this.volume

module.exports = Queue
/**
* Emitted when the queue is empty.
* @event Queue#end
*
* @example
* client.on('message', (message) => {
*
* const args = message.content.slice(settings.prefix.length).trim().split(/ +/g);
* const command = args.shift().toLowerCase();
*
* if(command === 'play'){
*
* let track = await client.player.play(message.member.voice.channel, args[0]);
*
* track.queue.on('end', () => {
* message.channel.send('The queue is empty, please add new tracks!');
* });
*
* }
*
* });
*/
/**
* Emitted when the voice channel is empty.
* @event Queue#channelEmpty
*/
/**
* Emitted when the track changes.
* @event Queue#trackChanged
* @param {Track} oldTrack The old track (playing before)
* @param {Track} newTrack The new track (currently playing)
* @param {Boolean} skipped Whether the change is due to the skip() function
*
* @example
* client.on('message', (message) => {
*
* const args = message.content.slice(settings.prefix.length).trim().split(/ +/g);
* const command = args.shift().toLowerCase();
*
* if(command === 'play'){
*
* let track = await client.player.play(message.member.voice.channel, args[0]);
*
* track.queue.on('trackChanged', (oldTrack, newTrack, skipped, repeatMode) => {
* if(repeatMode){
* message.channel.send(`Playing ${newTrack} again...`);
* } else {
* message.channel.send(`Now playing ${newTrack}...`);
* }
* });
*
* }
*
* });
*/
const Discord = require('discord.js')
const Queue = require('./Queue')
const Player = require('./Player')

@@ -10,11 +11,16 @@ /**

* @param {Object} videoData The video data for this track
* @param {Discord.User?} user The user who requested the track
* @param {Queue?} queue The queue in which is the track is
* @param {Discord.User | null} user The user who requested the track
* @param {Player} player
*/
constructor (videoData, user, queue) {
constructor (videoData, user, player) {
/**
* The track name
* The player instantiating the track
* @type {Player}
*/
this.player = player
/**
* The track title
* @type {string}
*/
this.name = videoData.title
this.title = videoData.title
/**

@@ -24,3 +30,3 @@ * The Youtube URL of the track

*/
this.url = videoData.link
this.url = videoData.link ?? videoData.url
/**

@@ -61,10 +67,13 @@ * The video duration (formatted).

this.fromPlaylist = videoData.fromPlaylist || false
/**
* The queue in which the track is
* @type {Queue}
*/
this.queue = queue
}
/**
* The queue in which the track is
* @type {Queue}
*/
get queue () {
return this.player.queues.find((queue) => queue.tracks.includes(this))
}
/**
* The track duration

@@ -71,0 +80,0 @@ * @type {number}

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc