New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

discord-player

Package Overview
Dependencies
Maintainers
1
Versions
367
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 3.2.1 to 3.3.1

src/Extractors/Discord.js

10

index.js

@@ -0,4 +1,10 @@

process.env.YTDL_NO_UPDATE = true
module.exports = {
version: require('./package.json').version,
Player: require('./src/Player')
Extractors: require('./src/Extractors/Extractor'),
Player: require('./src/Player'),
Queue: require('./src/Queue'),
Track: require('./src/Track'),
Util: require('./src/Util'),
version: require('./package.json').version
}

23

package.json
{
"name": "discord-player",
"version": "3.2.1",
"version": "3.3.1",
"description": "Complete framework to facilitate music commands using discord.js v12",

@@ -9,3 +9,3 @@ "main": "index.js",

"scripts": {
"test": "node index.js",
"test": "cd test && node index.js",
"generate-docs": "node_modules/.bin/jsdoc --configure .jsdoc.json --verbose"

@@ -34,20 +34,23 @@ },

"dependencies": {
"@types/node": "^14.14.7",
"chalk": "^4.1.0",
"discord-ytdl-core": "^5.0.0",
"jsdom": "^16.4.0",
"merge-options": "^3.0.4",
"moment": "^2.27.0",
"node-fetch": "^2.6.0",
"soundcloud-scraper": "^4.0.0",
"parse-ms": "^2.1.0",
"reverbnation-scraper": "^2.0.0",
"soundcloud-scraper": "^4.0.3",
"spotify-url-info": "^2.2.0",
"youtube-sr": "^2.0.5",
"ytdl-core": "^4.4.0"
"youtube-sr": "^4.0.0",
"ytdl-core": "^4.5.0"
},
"devDependencies": {
"@discordjs/opus": "^0.3.2",
"@discordjs/opus": "^0.4.0",
"@types/node": "14.14.31",
"discord.js": "^12.2.0",
"eslint": "^7.1.0",
"eslint": "^7.20.0",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-promise": "^4.3.1",
"eslint-plugin-standard": "^5.0.0",

@@ -54,0 +57,0 @@ "jsdoc": "^3.6.3",

@@ -5,3 +5,2 @@ # Discord Player

[![versionBadge](https://img.shields.io/npm/v/discord-player?style=for-the-badge)](https://npmjs.com/discord-player)
[![patreonBadge](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.herokuapp.com%2FAndroz2091%2Fpledges&style=for-the-badge)](https://patreon.com/Androz2091)

@@ -165,2 +164,5 @@ **Note**: this module uses recent discordjs features and requires discord.js version 12.

break;
case 'VideoUnavailable':
message.channel.send('This YouTube video is not available!');
break;
default:

@@ -167,0 +169,0 @@ message.channel.send(`Something went wrong... Error: ${error}`)

const ytdl = require('discord-ytdl-core')
const Discord = require('discord.js')
const ytsr = require('youtube-sr')
const ytsr = require('youtube-sr').default
const spotify = require('spotify-url-info')
const soundcloud = require('soundcloud-scraper')
const moment = require('moment')
const ms = require('parse-ms')
const Queue = require('./Queue')

@@ -12,2 +12,3 @@ const Track = require('./Track')

const Client = new soundcloud.Client()
const { VimeoExtractor, DiscordExtractor, FacebookExtractor, ReverbnationExtractor } = require('./Extractors/Extractor')

@@ -35,2 +36,11 @@ /**

* @property {boolean} [mono=false] Whether the mono output is enabled.
* @property {boolean} [mstlr=false] Whether M/S signal to L/R signal converter is enabled.
* @property {boolean} [mstrr=false] Whether M/S signal to R/R signal converter is enabled.
* @property {boolean} [compressor=false] Whether compressor filter is enabled.
* @property {boolean} [expander=false] Whether expander filter is enabled.
* @property {boolean} [softlimiter=false] Whether softlimiter filter is enabled.
* @property {boolean} [chorus=false] Whether chorus (single delay) filter is enabled.
* @property {boolean} [chorus2d=false] Whether chorus2d (two delays) filter is enabled.
* @property {boolean} [chorus3d=false] Whether chorus3d (three delays) filter is enabled.
* @property {boolean} [fadein=false] Whether fadein filter is enabled.
*/

@@ -57,3 +67,12 @@

mcompand: 'mcompand',
mono: 'pan=mono|c0=.5*c0+.5*c1'
mono: 'pan=mono|c0=.5*c0+.5*c1',
mstlr: 'stereotools=mode=ms>lr',
mstrr: 'stereotools=mode=ms>rr',
compressor: 'compand=points=-80/-105|-62/-80|-15.4/-15.4|0/-12|20/-7.6',
expander: 'compand=attacks=0:points=-80/-169|-54/-80|-49.5/-64.6|-41.1/-41.1|-25.8/-15|-10.8/-4.5|0/0|20/8.3',
softlimiter: 'compand=attacks=0:points=-80/-80|-12.4/-12.4|-6/-8|0/-6.8|20/-2.8',
chorus: 'chorus=0.7:0.9:55:0.4:0.25:2',
chorus2d: 'chorus=0.6:0.9:50|60:0.4|0.32:0.25|0.4:2|1.3',
chorus3d: 'chorus=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3',
fadein: 'afade=t=in:ss=0:d=10'
}

@@ -69,2 +88,5 @@

* @property {boolean} [autoSelfDeaf=true] Whether the bot should automatically turn off its headphones when joining a voice channel.
* @property {string} [quality='high'] Music quality (high or low)
* @property {boolean} [enableLive=false] If it should enable live contents
* @property {object} [ytdlRequestOptions={}] YTDL request options to use cookies, proxy etc..
*/

@@ -82,3 +104,6 @@

leaveOnEmptyCooldown: 0,
autoSelfDeaf: true
autoSelfDeaf: true,
quality: 'high',
enableLive: false,
ytdlRequestOptions: {}
}

@@ -100,2 +125,4 @@

this.util = Util
this.util.checkFFMPEG()
/**

@@ -133,9 +160,27 @@ * Discord.js client instance

this._resultsCollectors = new Discord.Collection()
/**
* @private
* @type {Discord.Collection<string, Timeout>}
*/
this._cooldownsTimeout = new Discord.Collection()
}
/**
* Returns all the available audio filters
* @type {Filters}
* @example const filters = require('discord-player').Player.AudioFilters
* console.log(`There are ${Object.keys(filters).length} filters!`)
*/
static get AudioFilters () {
return filters
}
/**
* @ignore
* @param {String} query
*/
resolveQueryType (query) {
resolveQueryType (query, forceType) {
if (forceType && typeof forceType === 'string') return forceType
if (this.util.isSpotifyLink(query)) {

@@ -149,2 +194,12 @@ return 'spotify-song'

return 'soundcloud-song'
} else if (this.util.isSpotifyPLLink(query)) {
return 'spotify-playlist'
} else if (this.util.isVimeoLink(query)) {
return 'vimeo'
} else if (FacebookExtractor.validateURL(query)) {
return 'facebook'
} else if (this.util.isReverbnationLink(query)) {
return 'reverbnation'
} else if (this.util.isDiscordAttachment(query)) {
return 'attachment'
} else {

@@ -161,5 +216,6 @@ return 'youtube-video-keywords'

* @param {boolean} firstResult
* @param {boolean} isAttachment
* @returns {Promise<Track>}
*/
_searchTracks (message, query, firstResult) {
_searchTracks (message, query, firstResult, isAttachment) {
return new Promise(async (resolve) => {

@@ -181,7 +237,119 @@ let tracks = []

} else if (queryType === 'soundcloud-song') {
const soundcloudData = await Client.getSongInfo(query).catch(() => {})
if (soundcloudData) {
updatedQuery = `${soundcloudData.author.name} - ${soundcloudData.title}`
queryType = 'youtube-video-keywords'
const data = await Client.getSongInfo(query).catch(() => { })
if (data) {
const track = new Track({
title: data.title,
url: data.url,
lengthSeconds: data.duration / 1000,
description: data.description,
thumbnail: data.thumbnail,
views: data.playCount,
author: data.author
}, message.author, this)
Object.defineProperty(track, 'soundcloud', {
get: () => data
})
tracks.push(track)
}
} else if (queryType === 'vimeo') {
const data = await VimeoExtractor.getInfo(this.util.getVimeoID(query)).catch(e => {})
if (!data) return this.emit('noResults', message, query)
const track = new Track({
title: data.title,
url: data.url,
thumbnail: data.thumbnail,
lengthSeconds: data.duration,
description: '',
views: 0,
author: data.author
}, message.author, this)
Object.defineProperties(track, {
arbitrary: {
get: () => true
},
stream: {
get: () => data.stream.url
}
})
tracks.push(track)
} else if (queryType === 'facebook') {
const data = await FacebookExtractor.getInfo(query).catch(e => {})
if (!data) return this.emit('noResults', message, query)
if (data.live && !this.options.enableLive) return this.emit('error', 'LiveVideo', message)
const track = new Track({
title: data.title,
url: data.url,
thumbnail: data.thumbnail,
lengthSeconds: data.duration,
description: data.description,
views: data.views || data.interactionCount,
author: data.author
}, message.author, this)
Object.defineProperties(track, {
arbitrary: {
get: () => true
},
stream: {
get: () => data.streamURL
}
})
tracks.push(track)
} else if (queryType === 'reverbnation') {
const data = await ReverbnationExtractor.getInfo(query).catch(() => {})
if (!data) return this.emit('noResults', message, query)
const track = new Track({
title: data.title,
url: data.url,
thumbnail: data.thumbnail,
lengthSeconds: data.duration / 1000,
description: '',
views: 0,
author: data.artist
}, message.author, this)
Object.defineProperties(track, {
arbitrary: {
get: () => true
},
stream: {
get: () => data.streamURL
}
})
tracks.push(track)
} else if (!!isAttachment || queryType === 'attachment') {
const data = await DiscordExtractor.getInfo(query).catch(() => {})
if (!data || !(data.format.startsWith('audio/') || data.format.startsWith('video/'))) return this.emit('noResults', message, query)
const track = new Track({
title: data.title,
url: query,
thumbnail: '',
lengthSeconds: 0,
description: '',
views: 0,
author: {
name: 'Media Attachment'
}
}, message.author, this)
Object.defineProperties(track, {
arbitrary: {
get: () => true
},
stream: {
get: () => query
}
})
tracks.push(track)
}

@@ -191,3 +359,3 @@

await ytsr.search(updatedQuery || query, { type: 'video' }).then((results) => {
if (results.length !== 0) {
if (results && results.length !== 0) {
tracks = results.map((r) => new Track(r, message.author, this))

@@ -256,2 +424,33 @@ }

/**
* Sets currently playing music duration
* @param {Discord.Message} message Discord message
* @param {number} time Time in ms
* @returns {Promise<void>}
*/
setPosition (message, time) {
return new Promise((resolve) => {
const queue = this.queues.find((g) => g.guildID === message.guild.id)
if (!queue) return this.emit('error', 'NotPlaying', message)
if (typeof time !== 'number' && !isNaN(time)) time = parseInt(time)
if (queue.playing.durationMS === time) return this.skip(message)
if (queue.voiceConnection.dispatcher.streamTime === time || (queue.voiceConnection.dispatcher.streamTime + queue.additionalStreamTime) === time) return resolve()
if (time < 0) this._playYTDLStream(queue, false).then(() => resolve())
this._playYTDLStream(queue, false, time)
.then(() => resolve())
})
}
/**
* Sets currently playing music duration
* @param {Discord.Message} message Discord message
* @param {number} time Time in ms
* @returns {Promise<void>}
*/
seek (message, time) {
return this.setPosition(message, time)
}
/**
* Check whether there is a music played in the server

@@ -265,2 +464,19 @@ * @param {Discord.Message} message

/**
* Moves to new voice channel
* @param {Discord.Message} message Message
* @param {Discord.VoiceChannel} channel Voice channel
*/
moveTo (message, channel) {
if (!channel || channel.type !== 'voice') return
const queue = this.queues.find((g) => g.guildID === message.guild.id)
if (!queue) return this.emit('error', 'NotPlaying', message)
if (queue.voiceConnection.channel.id === channel.id) return
queue.voiceConnection.dispatcher.pause()
channel.join()
.then(() => queue.voiceConnection.dispatcher.resume())
.catch(() => this.emit('error', 'UnableToJoin', message))
}
/**
* Add a track to the queue

@@ -329,2 +545,3 @@ * @ignore

async _handlePlaylist (message, query) {
this.emit('playlistParseStart', {}, message)
const playlist = await ytsr.getPlaylist(query)

@@ -336,2 +553,5 @@ if (!playlist) return this.emit('noResults', message, query)

playlist.requestedBy = message.author
this.emit('playlistParseEnd', playlist, message)
if (this.isPlaying(message)) {

@@ -343,3 +563,3 @@ const queue = this._addTracksToQueue(message, playlist.tracks)

const queue = await this._createQueue(message, track).catch((e) => this.emit('error', e, message))
this.emit('trackStart', message, queue.tracks[0])
this.emit('trackStart', message, queue.tracks[0], queue)
this._addTracksToQueue(message, playlist.tracks)

@@ -349,3 +569,137 @@ }

async _handleSpotifyPlaylist (message, query) {
this.emit('playlistParseStart', {}, message)
const playlist = await spotify.getData(query)
if (!playlist) return this.emit('noResults', message, query)
const tracks = []
let s = 0
for (let i = 0; i < playlist.tracks.items.length; i++) {
const query = `${playlist.tracks.items[i].track.artists[0].name} - ${playlist.tracks.items[i].track.name}`
const results = await ytsr.search(query, { type: 'video', limit: 1 })
if (results.length < 1) {
s++ // could be used later for skipped tracks due to result not being found
continue
}
tracks.push(results[0])
}
playlist.tracks = tracks.map((item) => new Track(item, message.author))
playlist.duration = playlist.tracks.reduce((prev, next) => prev + next.duration, 0)
playlist.thumbnail = playlist.images[0].url
playlist.requestedBy = message.author
this.emit('playlistParseEnd', playlist, message)
if (this.isPlaying(message)) {
const queue = this._addTracksToQueue(message, playlist.tracks)
this.emit('playlistAdd', message, queue, playlist)
} else {
const track = playlist.tracks.shift()
const queue = await this._createQueue(message, track).catch((e) => this.emit('error', e, message))
this.emit('trackStart', message, queue.tracks[0], queue)
this._addTracksToQueue(message, playlist.tracks)
}
}
async _handleSpotifyAlbum (message, query) {
const album = await spotify.getData(query)
if (!album) return this.emit('noResults', message, query)
const tracks = []
let s = 0
for (let i = 0; i < album.tracks.items.length; i++) {
const query = `${album.tracks.items[i].artists[0].name} - ${album.tracks.items[i].name}`
const results = await ytsr.search(query, { type: 'video' })
if (results.length < 1) {
s++ // could be used later for skipped tracks due to result not being found
continue
}
tracks.push(results[0])
}
album.tracks = tracks.map((item) => new Track(item, message.author))
album.duration = album.tracks.reduce((prev, next) => prev + next.duration, 0)
album.thumbnail = album.images[0].url
album.requestedBy = message.author
if (this.isPlaying(message)) {
const queue = this._addTracksToQueue(message, album.tracks)
this.emit('playlistAdd', message, queue, album)
} else {
const track = album.tracks.shift()
const queue = await this._createQueue(message, track).catch((e) => this.emit('error', e, message))
this.emit('trackStart', message, queue.tracks[0], queue)
this._addTracksToQueue(message, album.tracks)
}
}
async _handleSoundCloudPlaylist (message, query) {
const data = await Client.getPlaylist(query).catch(() => {})
if (!data) return this.emit('noResults', message, query)
const res = {
id: data.id,
title: data.title,
tracks: [],
author: data.author,
duration: 0,
thumbnail: data.thumbnail,
requestedBy: message.author
}
this.emit('playlistParseStart', res, message)
for (let i = 0; i < data.tracks.length; i++) {
const song = data.tracks[i]
const r = new Track({
title: song.title,
url: song.url,
lengthSeconds: song.duration / 1000,
description: song.description,
thumbnail: song.thumbnail || 'https://soundcloud.com/pwa-icon-192.png',
views: song.playCount || 0,
author: song.author || data.author
}, message.author, this, true)
Object.defineProperty(r, 'soundcloud', {
get: () => song
})
res.tracks.push(r)
}
if (!res.tracks.length) {
this.emit('playlistParseEnd', res, message)
return this.emit('error', 'ParseError', message)
}
res.duration = res.tracks.reduce((a, c) => a + c.lengthSeconds, 0)
this.emit('playlistParseEnd', res, message)
if (this.isPlaying(message)) {
const queue = this._addTracksToQueue(message, res.tracks)
this.emit('playlistAdd', message, queue, res)
} else {
const track = res.tracks.shift()
const queue = await this._createQueue(message, track).catch((e) => this.emit('error', e, message))
this.emit('trackStart', message, queue.tracks[0], queue)
this._addTracksToQueue(message, res.tracks)
}
}
/**
* Custom search function
* @param {string} query Search query
* @param {("youtube"|"soundcloud")} type Search type
* @returns {Promise<any[]>}
*/
async search (query, type = 'youtube') {
if (!query || typeof query !== 'string') return []
switch (type.toLowerCase()) {
case 'soundcloud':
return await Client.search(query, 'track').catch(() => {}) || []
default:
return await ytsr.search(query, { type: 'video' }).catch(() => {}) || []
}
}
/**
* Play a track in the server. Supported query types are `keywords`, `YouTube video links`, `YouTube playlists links`, `Spotify track link` or `SoundCloud song link`.

@@ -355,2 +709,3 @@ * @param {Discord.Message} message Discord `message`

* @param {boolean} firstResult Whether the bot should play the first song found on youtube with the given query
* @param {boolean} [isAttachment=false] If it should play it as attachment
* @returns {Promise<void>}

@@ -361,6 +716,26 @@ *

*/
async play (message, query, firstResult = false) {
if (this.util.isYTPlaylistLink(query)) {
async play (message, query, firstResult = false, isAttachment = false) {
if (this._cooldownsTimeout.has(`end_${message.guild.id}`)) {
clearTimeout(this._cooldownsTimeout.get(`end_${message.guild.id}`))
this._cooldownsTimeout.delete(`end_${message.guild.id}`)
}
if (!query || typeof query !== 'string') throw new Error('Play function requires search query but received none!')
// clean query
query = query.replace(/<(.+)>/g, '$1')
if (!this.util.isDiscordAttachment(query) && !isAttachment && this.util.isYTPlaylistLink(query)) {
return this._handlePlaylist(message, query)
}
if (this.util.isSpotifyPLLink(query)) {
return this._handleSpotifyPlaylist(message, query)
}
if (this.util.isSpotifyAlbumLink(query)) {
return this._handleSpotifyAlbum(message, query)
}
if (this.util.isSoundcloudPlaylist(query)) {
return this._handleSoundCloudPlaylist(message, query)
}
let trackToPlay

@@ -371,3 +746,3 @@ if (query instanceof Track) {

const videoData = await ytdl.getBasicInfo(query)
if (videoData.videoDetails.isLiveContent) return this.emit('error', 'LiveVideo', message)
if (videoData.videoDetails.isLiveContent && !this.options.enableLive) return this.emit('error', 'LiveVideo', message)
const lastThumbnail = videoData.videoDetails.thumbnails.length - 1 /* get the highest quality thumbnail */

@@ -386,3 +761,3 @@ trackToPlay = new Track({

} else {
trackToPlay = await this._searchTracks(message, query, firstResult)
trackToPlay = await this._searchTracks(message, query, firstResult, !!isAttachment || this.util.isDiscordAttachment(query))
}

@@ -395,3 +770,3 @@ if (trackToPlay) {

const queue = await this._createQueue(message, trackToPlay)
this.emit('trackStart', message, queue.tracks[0])
this.emit('trackStart', message, queue.tracks[0], queue)
}

@@ -426,3 +801,3 @@ }

if (!queue) return this.emit('error', 'NotPlaying', message)
// Pause the dispatcher
// Resume the dispatcher
queue.voiceConnection.dispatcher.resume()

@@ -580,3 +955,9 @@ queue.paused = false

const currentTrack = queue.tracks.shift()
queue.tracks = queue.tracks.sort(() => Math.random() - 0.5)
// Durstenfeld shuffle algorithm
for (let i = queue.tracks.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[queue.tracks[i], queue.tracks[j]] = [queue.tracks[j], queue.tracks[i]]
}
queue.tracks.unshift(currentTrack)

@@ -617,4 +998,5 @@ // Return the queue

* @param {Discord.Message} message
* @param {Object} options
* @param {boolean} options.timecodes
* @param {Object} [options]
* @param {boolean} [options.timecodes] Whether or not to show timecodes in the progress bar
* @param {boolean} [options.queue] Whether to show the progress bar for the whole queue (if false, only the current song)
* @returns {string}

@@ -628,7 +1010,7 @@ */

// Stream time of the dispatcher
const currentStreamTime = queue.voiceConnection.dispatcher
? queue.voiceConnection.dispatcher.streamTime + queue.additionalStreamTime
: 0
const previousTracksTime = queue.previousTracks.length > 0 ? queue.previousTracks.map((t) => t.durationMS).reduce((p, c) => p + c) : 0
const currentStreamTime = options && options.queue ? previousTracksTime + queue.currentStreamTime : queue.currentStreamTime
// Total stream time
const totalTime = queue.playing.durationMS
const totalTracksTime = queue.tracks.length > 0 ? queue.tracks.map((t) => t.durationMS).reduce((p, c) => p + c) : 0
const totalTime = options && options.queue ? previousTracksTime + totalTracksTime : queue.playing.durationMS
// Stream progress

@@ -641,4 +1023,5 @@ const index = Math.round((currentStreamTime / totalTime) * 15)

if (timecodes) {
const currentTimecode = (currentStreamTime >= 3600000 ? moment(currentStreamTime).format('H:mm:ss') : moment(currentStreamTime).format('m:ss'))
return `${currentTimecode} ┃ ${bar.join('')} ┃ ${queue.playing.duration}`
const currentTimecode = Util.buildTimecode(ms(currentStreamTime))
const endTimecode = Util.buildTimecode(ms(totalTime))
return `${currentTimecode} ┃ ${bar.join('')} ┃ ${endTimecode}`
} else {

@@ -649,4 +1032,5 @@ return `${bar.join('')}`

if (timecodes) {
const currentTimecode = (currentStreamTime >= 3600000 ? moment(currentStreamTime).format('H:mm:ss') : moment(currentStreamTime).format('m:ss'))
return `${currentTimecode} ┃ 🔘▬▬▬▬▬▬▬▬▬▬▬▬▬▬ ┃ ${queue.playing.duration}`
const currentTimecode = Util.buildTimecode(ms(currentStreamTime))
const endTimecode = Util.buildTimecode(ms(totalTime))
return `${currentTimecode} ┃ 🔘▬▬▬▬▬▬▬▬▬▬▬▬▬▬ ┃ ${endTimecode}`
} else {

@@ -675,28 +1059,40 @@ return '🔘▬▬▬▬▬▬▬▬▬▬▬▬▬▬'

// check if the bot is in a channel
if (!queue.voiceConnection || !queue.voiceConnection.channel) return
// process leaveOnEmpty checks
if (!this.options.leaveOnEmpty) return
// If the member leaves a voice channel
if (!oldState.channelID || newState.channelID) return
// If the channel is not empty
if (!this.util.isVoiceEmpty(queue.voiceConnection.channel)) return
setTimeout(() => {
// If the member joins a voice channel
if (!oldState.channelID || newState.channelID) {
const emptyTimeout = this._cooldownsTimeout.get(`empty_${oldState.guild.id}`)
const channelEmpty = this.util.isVoiceEmpty(queue.voiceConnection.channel)
if (!channelEmpty && emptyTimeout) {
clearTimeout(emptyTimeout)
this._cooldownsTimeout.delete(`empty_${oldState.guild.id}`)
}
} else {
// If the channel is not empty
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.delete(queue.guildID)
// Emit end event
this.emit('channelEmpty', queue.firstMessage, queue)
}, this.options.leaveOnEmptyCooldown || 0)
const timeout = 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.delete(queue.guildID)
// Emit end event
this.emit('channelEmpty', queue.firstMessage, queue)
}, this.options.leaveOnEmptyCooldown || 0)
this._cooldownsTimeout.set(`empty_${oldState.guild.id}`, timeout)
}
}
_playYTDLStream (queue, updateFilter) {
return new Promise((resolve) => {
const seekTime = updateFilter ? queue.voiceConnection.dispatcher.streamTime + queue.additionalStreamTime : undefined
_playYTDLStream (queue, updateFilter, seek) {
return new Promise(async (resolve) => {
const ffmeg = this.util.checkFFMPEG()
if (!ffmeg) return
const seekTime = typeof seek === 'number' ? seek : updateFilter ? queue.voiceConnection.dispatcher.streamTime + queue.additionalStreamTime : undefined
const encoderArgsFilters = []
Object.keys(queue.filters).forEach((filterName) => {
if (queue.filters[filterName]) {
encoderArgsFilters.push(filters[filterName])
encoderArgsFilters.push(this.filters[filterName])
}

@@ -710,9 +1106,22 @@ })

}
const newStream = ytdl(queue.playing.url, {
filter: 'audioonly',
opusEncoded: true,
encoderArgs,
seek: seekTime / 1000,
highWaterMark: 1 << 25
})
let newStream
if (!queue.playing.soundcloud && !queue.playing.arbitrary) {
newStream = ytdl(queue.playing.url, {
quality: this.options.quality === 'low' ? 'lowestaudio' : 'highestaudio',
filter: 'audioonly',
opusEncoded: true,
encoderArgs,
seek: seekTime / 1000,
highWaterMark: 1 << 25,
requestOptions: this.options.ytdlRequestOptions || {}
})
} else {
newStream = ytdl.arbitraryStream(queue.playing.soundcloud ? await queue.playing.soundcloud.downloadProgressive() : queue.playing.stream, {
opusEncoded: true,
encoderArgs,
seek: seekTime / 1000
})
}
setTimeout(() => {

@@ -740,2 +1149,10 @@ if (queue.stream) queue.stream.destroy()

})
newStream.on('error', (error) => {
if (error.message.includes('Video unavailable')) {
this.emit('error', 'VideoUnavailable', queue.firstMessage)
this._playTrack(queue, false)
} else {
this.emit('error', error, queue.firstMessage)
}
})
}, 1000)

@@ -753,11 +1170,14 @@ })

// If there isn't next music in the queue
if (queue.tracks.length === 1 && !queue.repeatMode && !firstPlay) {
if (queue.tracks.length === 1 && !queue.loopMode && !queue.repeatMode && !firstPlay) {
// Leave the voice channel
if (this.options.leaveOnEnd && !queue.stopped) {
setTimeout(() => {
// Remove the guild from the guilds list
this.queues.delete(queue.guildID)
const timeout = setTimeout(() => {
queue.voiceConnection.channel.leave()
// Remove the guild from the guilds list
this.queues.delete(queue.guildID)
}, this.options.leaveOnEndCooldown || 0)
this._cooldownsTimeout.set(`end_${queue.guildID}`, timeout)
}
// Remove the guild from the guilds list
this.queues.delete(queue.guildID)
// Emit stop event

@@ -791,4 +1211,4 @@ if (queue.stopped) {

* @param {Discord.Message} message
* @param {Track} track
* @param {Queue} queue
* @param {Track} track
*/

@@ -876,4 +1296,18 @@

* @event Player#error
* @param {string} error It can be `NotConnected`, `UnableToJoin` or `NotPlaying`.
* @param {string} error It can be `NotConnected`, `UnableToJoin`, `NotPlaying`, `ParseError`, `LiveVideo` or `VideoUnavailable`.
* @param {Discord.Message} message
*/
/**
* Emitted when discord-player attempts to parse playlist contents (mostly soundcloud playlists)
* @event Player#playlistParseStart
* @param {Object} playlist Raw playlist (unparsed)
* @param {Discord.Message} message The message
*/
/**
* Emitted when discord-player finishes parsing playlist contents (mostly soundcloud playlists)
* @event Player#playlistParseEnd
* @param {Object} playlist The playlist data (parsed)
* @param {Discord.Message} message The message
*/

@@ -100,4 +100,10 @@ const Discord = require('discord.js')

}
get currentStreamTime () {
return this.voiceConnection.dispatcher
? this.voiceConnection.dispatcher.streamTime + this.additionalStreamTime
: 0
}
}
module.exports = Queue

@@ -45,3 +45,3 @@ const Discord = require('discord.js')

*/
this.thumbnail = typeof videoData.thumbnail === 'object'
this.thumbnail = videoData.thumbnail && typeof videoData.thumbnail === 'object'
? videoData.thumbnail.url

@@ -48,0 +48,0 @@ : videoData.thumbnail

@@ -1,5 +0,10 @@

const ytsr = require('youtube-sr')
const ytsr = require('youtube-sr').default
const soundcloud = require('soundcloud-scraper')
const chalk = require('chalk')
const spotifySongRegex = (/https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:track\/|\?uri=spotify:track:)((\w|-){22})/)
const spotifyPlaylistRegex = (/https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:playlist\/|\?uri=spotify:playlist:)((\w|-){22})/)
const spotifyAlbumRegex = (/https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:album\/|\?uri=spotify:album:)((\w|-){22})/)
const vimeoRegex = (/(http|https)?:\/\/(www\.|player\.)?vimeo\.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|video\/|)(\d+)(?:|\/\?)/)
const facebookRegex = (/(https?:\/\/)(www\.|m\.)?(facebook|fb).com\/.*\/videos\/.*/)

@@ -11,2 +16,17 @@ module.exports = class Util {

static checkFFMPEG () {
try {
const prism = require('prism-media')
prism.FFmpeg.getInfo()
return true
} catch {
Util.alertFFMPEG()
return false
}
}
static alertFFMPEG () {
console.log(chalk.red('ERROR:'), 'FFMPEG is not installed. Install with "npm install ffmpeg-static" or download it here: https://ffmpeg.org/download.html.')
}
static isVoiceEmpty (channel) {

@@ -24,4 +44,12 @@ return channel.members.filter((member) => !member.user.bot).size === 0

static isSpotifyPLLink (query) {
return spotifyPlaylistRegex.test(query)
}
static isSpotifyAlbumLink (query) {
return spotifyAlbumRegex.test(query)
}
static isYTPlaylistLink (query) {
return ytsr.validate(query, 'PLAYLIST')
return ytsr.validate(query, 'PLAYLIST_ID')
}

@@ -32,2 +60,35 @@

}
static isSoundcloudPlaylist (query) {
return Util.isSoundcloudLink(query) && query.includes('/sets/')
}
static isVimeoLink (query) {
return vimeoRegex.test(query)
}
static getVimeoID (query) {
return Util.isVimeoLink(query) ? query.split('/').filter(x => !!x).pop() : null
}
static isFacebookLink (query) {
return facebookRegex.test(query)
}
static isReverbnationLink (query) {
return /https:\/\/(www.)?reverbnation.com\/(.+)\/song\/(.+)/.test(query)
}
static isDiscordAttachment (query) {
return /https:\/\/cdn.discordapp.com\/attachments\/(\d{17,19})\/(\d{17,19})\/(.+)/.test(query)
}
static buildTimecode (data) {
const items = Object.keys(data)
const required = ['days', 'hours', 'minutes', 'seconds']
const parsed = items.filter(x => required.includes(x)).map(m => data[m] > 0 ? data[m] : '')
const final = parsed.filter(x => !!x).map((x) => x.toString().padStart(2, '0')).join(':')
return final.length <= 3 ? `0:${final.padStart(2, '0') || 0}` : final
}
}

@@ -5,3 +5,3 @@ declare module 'discord-player' {

import { Playlist as YTSRPlaylist } from 'youtube-sr';
import { Stream } from 'stream';
import { Stream, Readable } from 'stream';

@@ -16,2 +16,7 @@ export const version: string;

static isYTVideoLink(query: string): boolean;
static isSoundcloudPlaylist(query: string): boolean;
static isVimeoLink(query: string): boolean;
static getVimeoID(query: string): string;
static isFacebookLink(query: string): boolean;
static buildTimecode(data: any): string;
}

@@ -28,2 +33,3 @@

public static get AudioFilters(): PlayerFilters;
public isPlaying(message: Message): boolean;

@@ -44,3 +50,3 @@ public setFilters(message: Message, newFilters: Partial<Filters>): Promise<void>;

public shuffle(message: Message): Queue;
public remove(message: Message, trackOrPosition: Track|number): Track;
public remove(message: Message, trackOrPosition: Track | number): Track;
public createProgressBar(message: Message, progressBarOptions: ProgressBarOptions): string;

@@ -52,2 +58,3 @@

}
type MusicQuality = 'high' | 'low';
interface PlayerOptions {

@@ -60,4 +67,34 @@ leaveOnEnd: boolean;

autoSelfDeaf: boolean;
quality: MusicQuality;
}
type Filters = 'bassboost' | '8D' | 'vaporwave' | 'nightcore'| 'phaser' | 'tremolo' | 'vibrato' | 'reverse' | 'treble' | 'normalizer' | 'surrounding' | 'pulsator' | 'subboost' | 'karaoke' | 'flanger' | 'gate' | 'haas' | 'mcompand';
type Filters =
| 'bassboost'
| '8D'
| 'vaporwave'
| 'nightcore'
| 'phaser'
| 'tremolo'
| 'vibrato'
| 'reverse'
| 'treble'
| 'normalizer'
| 'surrounding'
| 'pulsator'
| 'subboost'
| 'karaoke'
| 'flanger'
| 'gate'
| 'haas'
| 'mcompand'
| 'mono'
| 'mstlr'
| 'mstrr'
| 'compressor'
| 'expander'
| 'softlimiter'
| 'chorus'
| 'chorus2d'
| 'chorus3d'
| 'fadein';
type FiltersStatuses = {

@@ -79,2 +116,3 @@ [key in Filters]: boolean;

type Playlist = YTSRPlaylist & CustomPlaylist;
type PlayerError = 'NotConnected' | 'UnableToJoin' | 'NotPlaying' | 'LiveVideo' | 'ParseError' | 'VideoUnavailable';
interface PlayerEvents {

@@ -93,3 +131,5 @@ searchResults: [Message, string, Track[]];

queueEnd: [Message, Queue];
error: [string, Message];
error: [PlayerError, Message];
playlistParseStart: [any, Message];
playlistParseEnd: [any, Message];
}

@@ -136,2 +176,98 @@ class Queue {

}
export interface RawExtractedData {
title: string;
format: string;
size: number;
sizeFormat: "MB";
stream: Readable;
}
export interface VimeoExtractedData {
id: number;
duration: number;
title: string;
url: string;
thumbnail: string;
width: number;
height: number;
stream: {
cdn: string;
fps: number;
width: number;
height: number;
id: string;
mime: string;
origin: string;
profile: number;
quality: string;
url: string;
};
author: {
accountType: string;
id: number;
name: string;
url: string;
avatar: string;
}
}
interface FacebookExtractedData {
name: string;
title: string;
description: string;
rawVideo: string;
thumbnail: string;
uploadedAt: Date;
duration: string;
interactionCount: number;
streamURL: string;
publishedAt: Date;
width: number;
height: number;
nsfw: boolean;
genre: string;
keywords: string[];
comments: number;
size: string;
quality: string;
author: {
type: string;
name: string;
url: string;
};
publisher: {
type: string;
name: string;
url: string;
avatar: string;
};
url: string;
shares: string;
views: string;
}
class Discord {
static getInfo(url: string): Promise<RawExtractedData>;
static download(url: string): Promise<Readable>;
}
class Facebook {
static validateURL(url: string): boolean;
static download(url: string): Promise<Readable>;
static getInfo(url: string): Promise<FacebookExtractedData>;
}
class Vimeo {
static getInfo(id: number): Promise<VimeoExtractedData>;
static download(id: number): Promise<Readable>;
}
interface Extractors {
DiscordExtractor: Discord;
FacebookExtractor: Facebook;
VimeoExtractor: Vimeo;
}
export const Extractors: Extractors;
}
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