From a0cdcbc46184386d1bc08c82d026d2db3f1a5819 Mon Sep 17 00:00:00 2001 From: Snowflake <46562212+Snowflake107@users.noreply.github.com> Date: Sat, 4 Jul 2020 17:08:49 +0545 Subject: [PATCH] Spotify support & bug fixes (#40) Co-authored-by: Androz2091 --- package.json | 8 +- src/Player.js | 20 ++-- src/Queue.js | 258 +++++++++++++++++++++++++------------------------- src/Track.js | 5 + 4 files changed, 152 insertions(+), 139 deletions(-) diff --git a/package.json b/package.json index fb8d35f..e8e8e04 100644 --- a/package.json +++ b/package.json @@ -28,16 +28,16 @@ }, "homepage": "https://github.com/Androz2091/discord-player#readme", "dependencies": { - "@discordjs/opus": "^0.3.2", "discord-ytdl-core": "^4.0.2", "merge-options": "^2.0.0", "node-fetch": "^2.6.0", - "simple-youtube-api": "^5.2.1", - "ytpl": "^0.1.21", + "spotify-url-info": "^1.3.1", + "ytpl": "^0.1.22", "ytsr": "^0.1.15" }, "devDependencies": { - "discord.js": "discordjs/discord.js", + "@discordjs/opus": "^0.3.2", + "discord.js": "^12.2.0", "eslint": "^7.1.0", "eslint-config-standard": "^14.1.1", "eslint-plugin-import": "^2.20.2", diff --git a/src/Player.js b/src/Player.js index 9235da6..db3743b 100644 --- a/src/Player.js +++ b/src/Player.js @@ -2,7 +2,7 @@ const ytdl = require('discord-ytdl-core') const Discord = require('discord.js') const ytsr = require('ytsr') const ytpl = require('ytpl') - +const spotify = require('spotify-url-info') const Queue = require('./Queue') const Track = require('./Track') @@ -176,10 +176,15 @@ class Player { } } } + 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 matchURL = query.match(/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/) - if (matchURL) { - query = matchURL[1] + const matchYoutubeURL = query.match(/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/) + if (matchYoutubeURL) { + query = matchYoutubeURL[1] } ytsr(query, (err, results) => { if (results.items.length < 1) return resolve([]) @@ -814,10 +819,11 @@ class Player { if (queue.stream) queue.stream.destroy() queue.stream = newStream queue.voiceConnection.play(newStream, { - type: 'opus' + type: 'opus', + bitrate: 'auto' }) if (currentStreamTime) { - queue.voiceConnection.dispatcher.streamTime += currentStreamTime + queue.playing.streamTime += currentStreamTime } queue.voiceConnection.dispatcher.setVolumeLogarithmic(queue.calculatedVolume / 200) // When the track starts @@ -826,6 +832,8 @@ class Player { }) // When the track ends queue.voiceConnection.dispatcher.on('finish', () => { + // reset streamTime + if (queue.repeatMode) queue.playing.streamTime = 0 // Play the next track return this._playTrack(queue.guildID, false) }) diff --git a/src/Queue.js b/src/Queue.js index d337d58..86a4f94 100644 --- a/src/Queue.js +++ b/src/Queue.js @@ -1,129 +1,129 @@ -const Discord = require('discord.js') -const { EventEmitter } = require('events') -const Track = require('./Track') - -/** - * Represents a guild queue. - */ -class Queue extends EventEmitter { - /** - * @param {Discord.Snowflake} guildID ID of the guild this queue is for. - */ - constructor (guildID) { - super() - /** - * ID of the guild this queue is for. - * @type {Discord.Snowflake} - */ - this.guildID = guildID - /** - * The voice connection of this queue. - * @type {Discord.VoiceConnection} - */ - this.voiceConnection = null - /** - * The song currently played. - * @type {Track} - */ - this.playing = null - /** - * The tracks of this queue. The first one is currenlty playing and the others are going to be played. - * @type {Track[]} - */ - this.tracks = [] - /** - * Whether the stream is currently stopped. - * @type {boolean} - */ - this.stopped = false - /** - * Whether the last track was skipped. - * @type {boolean} - */ - this.lastSkipped = false - /** - * The stream volume of this queue. (0-100) - * @type {number} - */ - this.volume = 100 - /** - * Whether the stream is currently paused. - * @type {boolean} - */ - this.paused = true - /** - * Whether the repeat mode is enabled. - * @type {boolean} - */ - this.repeatMode = false - /** - * Filters status - * @type {Object} - */ - this.filters = {} - } - - get calculatedVolume () { - return this.filters.bassboost ? this.volume + 40 : 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 { EventEmitter } = require('events') +const Track = require('./Track') + +/** + * Represents a guild queue. + */ +class Queue extends EventEmitter { + /** + * @param {Discord.Snowflake} guildID ID of the guild this queue is for. + */ + constructor (guildID) { + super() + /** + * ID of the guild this queue is for. + * @type {Discord.Snowflake} + */ + this.guildID = guildID + /** + * The voice connection of this queue. + * @type {Discord.VoiceConnection} + */ + this.voiceConnection = null + /** + * The song currently played. + * @type {Track} + */ + this.playing = null + /** + * The tracks of this queue. The first one is currenlty playing and the others are going to be played. + * @type {Track[]} + */ + this.tracks = [] + /** + * Whether the stream is currently stopped. + * @type {boolean} + */ + this.stopped = false + /** + * Whether the last track was skipped. + * @type {boolean} + */ + this.lastSkipped = false + /** + * The stream volume of this queue. (0-100) + * @type {number} + */ + this.volume = 100 + /** + * Whether the stream is currently paused. + * @type {boolean} + */ + this.paused = true + /** + * Whether the repeat mode is enabled. + * @type {boolean} + */ + this.repeatMode = false + /** + * Filters status + * @type {Object} + */ + this.filters = {} + } + + get calculatedVolume () { + 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}...`); + * } + * }); + * + * } + * + * }); + */ diff --git a/src/Track.js b/src/Track.js index 91cf172..ce5e218 100644 --- a/src/Track.js +++ b/src/Track.js @@ -56,6 +56,11 @@ class Track { * @type {Queue} */ this.queue = queue + + /** + * Stream time of the track (available on applying filters) + */ + this.streamTime = 0 } /**