diff --git a/docs/faq/custom_filters.md b/docs/faq/custom_filters.md index 35b39e5..fd3cfa9 100644 --- a/docs/faq/custom_filters.md +++ b/docs/faq/custom_filters.md @@ -1,4 +1,4 @@ -# How to add custom audio filters +# How to add custom audio filters? Audio filters in **Discord Player** are **[FFmpeg audio filters](http://ffmpeg.org/ffmpeg-all.html#Audio-Filters)**. You can add your own audio filter like this: diff --git a/docs/faq/live_video.md b/docs/faq/live_video.md index 1882bd4..40eb4be 100644 --- a/docs/faq/live_video.md +++ b/docs/faq/live_video.md @@ -1,4 +1,4 @@ -# How to play live videos +# How to play live videos? You cannot play live videos by default. If you need to play the live video, just add this option: diff --git a/package.json b/package.json index 8092060..64a3309 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-player", - "version": "4.0.7", + "version": "4.0.8", "description": "Complete framework to facilitate music commands using discord.js", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -51,9 +51,9 @@ "homepage": "https://discord-player.js.org", "dependencies": { "discord-ytdl-core": "^5.0.3", - "soundcloud-scraper": "^4.0.4", + "soundcloud-scraper": "^5.0.0", "spotify-url-info": "^2.2.0", - "youtube-sr": "^4.0.6", + "youtube-sr": "^4.0.7", "ytdl-core": "^4.7.0" }, "devDependencies": { @@ -61,7 +61,7 @@ "@babel/core": "^7.13.16", "@babel/preset-env": "^7.13.15", "@babel/preset-typescript": "^7.13.0", - "@discord-player/extractor": "^2.0.0", + "@discord-player/extractor": "^3.0.0", "@discordjs/opus": "^0.5.0", "@types/node": "^14.14.41", "@types/ws": "^7.4.1", diff --git a/src/Player.ts b/src/Player.ts index d10b4f0..05c773c 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -185,16 +185,42 @@ export class Player extends EventEmitter { /https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:track\/|\?uri=spotify:track:)((\w|-){22})/ ); if (matchSpotifyURL) { - const spotifyData = await spotify.getPreview(query).catch(() => {}); + const spotifyData = await spotify.getData(query).catch(() => {}); if (spotifyData) { - const searchString = this.options.disableArtistSearch - ? spotifyData.title - : `${spotifyData.artist} - ${spotifyData.title}`; - tracks = await Util.ytSearch(searchString, { - user: message.author, - player: this, - limit: 1 + const spotifyTrack = new Track(this, { + title: spotifyData.name, + description: spotifyData.description ?? '', + author: spotifyData.artists[0]?.name ?? 'Unknown Artist', + url: spotifyData.external_urls?.spotify ?? query, + thumbnail: + spotifyData.album?.images[0]?.url ?? spotifyData.preview_url?.length + ? `https://i.scdn.co/image/${spotifyData.preview_url?.split('?cid=')[1]}` + : 'https://www.scdn.co/i/_global/twitter_card-default.jpg', + duration: Util.buildTimeCode(Util.parseMS(spotifyData.duration_ms)), + views: 0, + requestedBy: message.author, + fromPlaylist: false, + source: 'spotify' }); + + if (this.options.fetchBeforeQueued) { + const searchQueryString = this.options.disableArtistSearch + ? spotifyTrack.title + : `${spotifyTrack.title}${' - ' + spotifyTrack.author}`; + const ytv = await YouTube.search(searchQueryString, { + limit: 1, + type: 'video' + }).catch((e) => {}); + + if (ytv && ytv[0]) + Util.define({ + target: spotifyTrack, + prop: 'backupLink', + value: ytv[0].url + }); + } + + tracks = [spotifyTrack]; } } } @@ -207,33 +233,88 @@ export class Player extends EventEmitter { if (!playlist) return void this.emit(PlayerEvents.NO_RESULTS, message, query); // tslint:disable-next-line:no-shadowed-variable - let tracks = await Promise.all( - playlist.tracks.items.map(async (item: any) => { - const sq = - queryType === 'spotify_album' - ? `${ - this.options.disableArtistSearch - ? item.artists[0].name - : `${item.artists[0].name} - ` - }${item.name ?? item.track.name}` - : `${ - this.options.disableArtistSearch - ? item.track.artists[0].name - : `${item.track.artists[0].name} - ` - }${item.name ?? item.track.name}`; + let tracks: Track[] = []; - const data = await Util.ytSearch(sq, { - limit: 1, - player: this, - user: message.author, - pl: true - }); + if (playlist.type !== 'playlist') + tracks = await Promise.all( + playlist.tracks.items.map(async (m: any) => { + const data = new Track(this, { + title: m.name ?? '', + description: m.description ?? '', + author: m.artists[0]?.name ?? 'Unknown Artist', + url: m.external_urls?.spotify ?? query, + thumbnail: + playlist.images[0]?.url ?? m.preview_url?.length + ? `https://i.scdn.co/image/${m.preview_url?.split('?cid=')[1]}` + : 'https://www.scdn.co/i/_global/twitter_card-default.jpg', + duration: Util.buildTimeCode(Util.parseMS(m.duration_ms)), + views: 0, + requestedBy: message.author, + fromPlaylist: true, + source: 'spotify' + }); - if (data.length) return data[0]; - }) - ); + if (this.options.fetchBeforeQueued) { + const searchQueryString = this.options.disableArtistSearch + ? data.title + : `${data.title}${' - ' + data.author}`; + const ytv = await YouTube.search(searchQueryString, { + limit: 1, + type: 'video' + }).catch((e) => {}); + + if (ytv && ytv[0]) + Util.define({ + target: data, + prop: 'backupLink', + value: ytv[0].url + }); + } + + return data; + }) + ); + else { + tracks = await Promise.all( + playlist.tracks.items.map(async (m: any) => { + const data = new Track(this, { + title: m.track.name ?? '', + description: m.track.description ?? '', + author: m.track.artists[0]?.name ?? 'Unknown Artist', + url: m.track.external_urls?.spotify ?? query, + thumbnail: + playlist.images[0]?.url ?? m.track.preview_url?.length + ? `https://i.scdn.co/image/${m.track.preview_url?.split('?cid=')[1]}` + : 'https://www.scdn.co/i/_global/twitter_card-default.jpg', + duration: Util.buildTimeCode(Util.parseMS(m.track.duration_ms)), + views: 0, + requestedBy: message.author, + fromPlaylist: true, + source: 'spotify' + }); + + if (this.options.fetchBeforeQueued) { + const searchQueryString = this.options.disableArtistSearch + ? data.title + : `${data.title}${' - ' + data.author}`; + const ytv = await YouTube.search(searchQueryString, { + limit: 1, + type: 'video' + }).catch((e) => {}); + + if (ytv && ytv[0]) + Util.define({ + target: data, + prop: 'backupLink', + value: ytv[0].url + }); + } + + return data; + }) + ); + } - tracks = tracks.filter((f) => !!f); if (!tracks.length) return void this.emit(PlayerEvents.NO_RESULTS, message, query); const pl = { @@ -250,7 +331,7 @@ export class Player extends EventEmitter { const queue = this._addTracksToQueue(message, tracks); this.emit(PlayerEvents.PLAYLIST_ADD, message, queue, pl); } else { - const track = tracks.shift(); + const track = tracks[0]; const queue = (await this._createQueue(message, track).catch( (e) => void this.emit(PlayerEvents.ERROR, e, message) )) as Queue; @@ -306,7 +387,7 @@ export class Player extends EventEmitter { const queue = this._addTracksToQueue(message, tracks); this.emit(PlayerEvents.PLAYLIST_ADD, message, queue, playlist); } else { - const track = tracks.shift(); + const track = tracks[0]; const queue = (await this._createQueue(message, track).catch( (e) => void this.emit(PlayerEvents.ERROR, e, message) )) as Queue; @@ -361,7 +442,7 @@ export class Player extends EventEmitter { const queue = this._addTracksToQueue(message, res.tracks); this.emit(PlayerEvents.PLAYLIST_ADD, message, queue, res); } else { - const track = res.tracks.shift(); + const track = res.tracks[0]; const queue = (await this._createQueue(message, track).catch( (e) => void this.emit(PlayerEvents.ERROR, e, message) )) as Queue; @@ -475,7 +556,7 @@ export class Player extends EventEmitter { author: data.author, views: data.views, engine: data.engine, - source: 'arbitrary', + source: data.source ?? 'arbitrary', fromPlaylist: false, requestedBy: message.author, url: data.url @@ -959,7 +1040,7 @@ export class Player extends EventEmitter { const extractor = Util.require('@discord-player/extractor'); if (!extractor) throw new PlayerError("Cannot call 'Player.lyrics()' without '@discord-player/extractor'"); - const data = await extractor.Lyrics(query); + const data = await extractor.Lyrics.init().search(query); if (Array.isArray(data)) return null; return data; @@ -1054,7 +1135,7 @@ export class Player extends EventEmitter { if (!oldState.channelID || newState.channelID) { const emptyTimeout = this._cooldownsTimeout.get(`empty_${oldState.guild.id}`); - // @todo: stage channels + // @todo: make stage channels stable const channelEmpty = Util.isVoiceEmpty(queue.voiceConnection.channel as VoiceChannel); if (!channelEmpty && emptyTimeout) { clearTimeout(emptyTimeout); @@ -1254,8 +1335,32 @@ export class Player extends EventEmitter { } let newStream: any; - if (queue.playing.raw.source === 'youtube') { - newStream = ytdl(queue.playing.url, { + + // modify spotify + if (queue.playing.raw.source === 'spotify' && !(queue.playing as any).backupLink) { + const searchQueryString = this.options.disableArtistSearch + ? queue.playing.title + : `${queue.playing.title}${' - ' + queue.playing.author}`; + const yteqv = await YouTube.search(searchQueryString, { type: 'video', limit: 1 }).catch(() => {}); + + if (!yteqv || !yteqv.length) + return void this.emit( + PlayerEvents.ERROR, + PlayerErrorEventCodes.VIDEO_UNAVAILABLE, + queue.firstMessage, + queue.playing, + new PlayerError('Could not find alternative track on youtube!', 'SpotifyTrackError') + ); + + Util.define({ + target: queue.playing, + prop: 'backupLink', + value: yteqv[0].url + }); + } + + if (queue.playing.raw.source === 'youtube' || queue.playing.raw.source === 'spotify') { + newStream = ytdl((queue.playing as any).backupLink ?? queue.playing.url, { opusEncoded: true, encoderArgs: queue.playing.raw.live ? [] : encoderArgs, seek: seekTime / 1000, @@ -1281,8 +1386,7 @@ export class Player extends EventEmitter { queue.stream = newStream; queue.voiceConnection.play(newStream, { type: 'opus', - bitrate: 'auto', - volume: Util.isRepl() ? false : undefined + bitrate: 'auto' }); if (seekTime) { @@ -1443,6 +1547,7 @@ export default Player; * @property {Boolean} [useSafeSearch=false] If it should use `safe search` method for youtube searches * @property {Boolean} [disableAutoRegister=false] If it should disable auto-registeration of `@discord-player/extractor` * @property {Boolean} [disableArtistSearch=false] If it should disable artist search for spotify + * @property {Boolean} [fetchBeforeQueued=false] If it should fetch all songs loaded from spotify before playing */ /** @@ -1518,7 +1623,7 @@ export default Player; * @property {String} thumbnail The thumbnail * @property {String} image The image * @property {String} url The url - * @property {Object} artist The artust info + * @property {Object} artist The artist info * @property {String} [artist.name] The name of the artist * @property {Number} [artist.id] The ID of the artist * @property {String} [artist.url] The profile link of the artist diff --git a/src/Structures/Queue.ts b/src/Structures/Queue.ts index a8fa7a5..7398466 100644 --- a/src/Structures/Queue.ts +++ b/src/Structures/Queue.ts @@ -167,7 +167,7 @@ export class Queue extends EventEmitter { /** * Sets audio filters in this player * @param {QueueFilters} filters Audio filters to set - * @type {Promise} + * @returns {Promise} */ setFilters(filters: QueueFilters): Promise { return this.player.setFilters(this.firstMessage, filters); @@ -175,7 +175,7 @@ export class Queue extends EventEmitter { /** * Returns array of all enabled filters - * @type {String[]} + * @returns {String[]} */ getFiltersEnabled(): string[] { const filters: string[] = []; @@ -189,7 +189,7 @@ export class Queue extends EventEmitter { /** * Returns all disabled filters - * @type {String[]} + * @returns {String[]} */ getFiltersDisabled(): string[] { const enabled = this.getFiltersEnabled(); @@ -197,6 +197,14 @@ export class Queue extends EventEmitter { return Object.keys(this.filters).filter((f) => !enabled.includes(f)); } + /** + * Destroys this queue + * @returns {Boolean} + */ + destroy() { + return this.player.stop(this.firstMessage); + } + /** * String representation of this Queue * @returns {String} diff --git a/src/Structures/Track.ts b/src/Structures/Track.ts index b2759f1..e24b791 100644 --- a/src/Structures/Track.ts +++ b/src/Structures/Track.ts @@ -134,6 +134,14 @@ export class Track { .reduce((a, c) => a + c, 0); } + /** + * Returns source of this track + * @type {TrackSource} + */ + get source() { + return this.raw.source ?? 'arbitrary'; + } + /** * String representation of this track * @returns {String} diff --git a/src/types/types.ts b/src/types/types.ts index 629188d..bc9c541 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -14,11 +14,12 @@ export interface PlayerOptions { useSafeSearch?: boolean; disableAutoRegister?: boolean; disableArtistSearch?: boolean; + fetchBeforeQueued?: boolean; } export type FiltersName = keyof QueueFilters; -export type TrackSource = 'soundcloud' | 'youtube' | 'arbitrary'; +export type TrackSource = 'soundcloud' | 'youtube' | 'spotify' | 'arbitrary'; export interface TrackData { title: string; @@ -91,6 +92,7 @@ export interface ExtractorModelData { url: string; version?: string; important?: boolean; + source?: TrackSource; } export interface PlayerProgressbarOptions { diff --git a/src/utils/Util.ts b/src/utils/Util.ts index 7826f5d..9ba4bf0 100644 --- a/src/utils/Util.ts +++ b/src/utils/Util.ts @@ -65,8 +65,8 @@ export class Util { * @returns {QueryType} */ static getQueryType(query: string): QueryType { - if (SoundcloudValidateURL(query) && !query.includes('/sets/')) return 'soundcloud_track'; - if (SoundcloudValidateURL(query) && query.includes('/sets/')) return 'soundcloud_playlist'; + if (SoundcloudValidateURL(query, 'track')) return 'soundcloud_track'; + if (SoundcloudValidateURL(query, 'playlist') || query.includes('/sets/')) return 'soundcloud_playlist'; if (spotifySongRegex.test(query)) return 'spotify_song'; if (spotifyAlbumRegex.test(query)) return 'spotify_album'; if (spotifyPlaylistRegex.test(query)) return 'spotify_playlist'; @@ -166,28 +166,6 @@ export class Util { }); } - /** - * Checks if this system is running in replit.com - * @returns {Boolean} - */ - static isRepl(): boolean { - if ('DP_REPL_NOCHECK' in process.env) return false; - - const REPL_IT_PROPS = [ - 'REPL_SLUG', - 'REPL_OWNER', - 'REPL_IMAGE', - 'REPL_PUBKEYS', - 'REPL_ID', - 'REPL_LANGUAGE', - 'REPLIT_DB_URL' - ]; - - for (const prop of REPL_IT_PROPS) if (prop in process.env) return true; - - return false; - } - /** * Checks if the given voice channel is empty * @param {DiscordVoiceChannel} channel The voice channel @@ -226,6 +204,22 @@ export class Util { return null; } } + + /** + * Defines a property in the given object + * @param {any} target The target + * @param {any} prop The property to define + * @param {any} value The value + * @returns {void} + */ + static define(ops: { target: any; prop: any; value: any; enumerate?: boolean }) { + Object.defineProperty(ops.target, ops.prop, { + value: ops.value, + writable: true, + enumerable: Boolean(ops.enumerate), + configurable: true + }); + } } export default Util; diff --git a/yarn.lock b/yarn.lock index cf8a6a4..24d141b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -871,13 +871,13 @@ "@babel/helper-validator-identifier" "^7.12.11" to-fast-properties "^2.0.0" -"@discord-player/extractor@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@discord-player/extractor/-/extractor-2.0.0.tgz#3879e51d64b72d4dcee9338bdece5251d006c746" - integrity sha512-qNyF0dkLNRYvtVtLLO022RV8DzToCPJqbuAOqSWCSdkXiKSGoqTdKcZI9I/Lb87mchYuuakOXyPRvRwkAruX6w== +"@discord-player/extractor@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@discord-player/extractor/-/extractor-3.0.0.tgz#2baa733da054b991f83417054cad1575cf57c4ab" + integrity sha512-6A2QmrbQXTpv1oequiAYQMnWzvv1fu9Yg0GJwAMoLUXZjvb5Yoak2lnohsDI1Hc14LW//Z5xyLGtmFivTWTXDQ== dependencies: - genius-lyrics "^4.2.7" - jsdom "^16.5.2" + genius-lyrics "^4.2.9" + jsdom "^16.5.3" node-fetch "^2.6.1" reverbnation-scraper "^2.0.0" @@ -940,6 +940,14 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@types/node-fetch@^2.5.10": + version "2.5.10" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.10.tgz#9b4d4a0425562f9fcea70b12cb3fcdd946ca8132" + integrity sha512-IpkX0AasN44hgEad0gEF/V6EgR5n69VEqPEgnmoM8GsIGro3PowbWs4tR6IhxUTyPLpOn+fiGG6nrQhcmoCuIQ== + dependencies: + "@types/node" "*" + form-data "^3.0.0" + "@types/node@*", "@types/node@^14.14.41": version "14.14.41" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.41.tgz#d0b939d94c1d7bd53d04824af45f1139b8c45615" @@ -1405,29 +1413,29 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -cheerio-select-tmp@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/cheerio-select-tmp/-/cheerio-select-tmp-0.1.1.tgz#55bbef02a4771710195ad736d5e346763ca4e646" - integrity sha512-YYs5JvbpU19VYJyj+F7oYrIE2BOll1/hRU7rEy/5+v9BzkSo3bK81iAeeQEMI92vRIxz677m72UmJUiVwwgjfQ== +cheerio-select@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-1.4.0.tgz#3a16f21e37a2ef0f211d6d1aa4eff054bb22cdc9" + integrity sha512-sobR3Yqz27L553Qa7cK6rtJlMDbiKPdNywtR95Sj/YgfpLfy0u6CGJuaBKe5YE/vTc23SCRKxWSdlon/w6I/Ew== dependencies: - css-select "^3.1.2" - css-what "^4.0.0" - domelementtype "^2.1.0" - domhandler "^4.0.0" - domutils "^2.4.4" + css-select "^4.1.2" + css-what "^5.0.0" + domelementtype "^2.2.0" + domhandler "^4.2.0" + domutils "^2.6.0" -cheerio@^1.0.0-rc.3: - version "1.0.0-rc.5" - resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.5.tgz#88907e1828674e8f9fee375188b27dadd4f0fa2f" - integrity sha512-yoqps/VCaZgN4pfXtenwHROTp8NG6/Hlt4Jpz2FEP0ZJQ+ZUkVDd0hAPDNKhj3nakpfPt/CNs57yEtxD1bXQiw== +cheerio@^1.0.0-rc.9: + version "1.0.0-rc.9" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.9.tgz#a3ae6b7ce7af80675302ff836f628e7cb786a67f" + integrity sha512-QF6XVdrLONO6DXRF5iaolY+odmhj2CLj+xzNod7INPWMi/x9X4SOylH0S/vaPpX+AUU6t04s34SQNh7DbkuCng== dependencies: - cheerio-select-tmp "^0.1.0" - dom-serializer "~1.2.0" - domhandler "^4.0.0" - entities "~2.1.0" - htmlparser2 "^6.0.0" - parse5 "^6.0.0" - parse5-htmlparser2-tree-adapter "^6.0.0" + cheerio-select "^1.4.0" + dom-serializer "^1.3.1" + domhandler "^4.2.0" + htmlparser2 "^6.1.0" + parse5 "^6.0.1" + parse5-htmlparser2-tree-adapter "^6.0.1" + tslib "^2.2.0" chokidar@^3.4.0: version "3.5.1" @@ -1648,21 +1656,21 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -css-select@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-3.1.2.tgz#d52cbdc6fee379fba97fb0d3925abbd18af2d9d8" - integrity sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA== +css-select@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.2.tgz#8b52b6714ed3a80d8221ec971c543f3b12653286" + integrity sha512-nu5ye2Hg/4ISq4XqdLY2bEatAcLIdt3OYGFc9Tm9n7VSlFBcfRv0gBNksHRgSdUDQGtN3XrZ94ztW+NfzkFSUw== dependencies: boolbase "^1.0.0" - css-what "^4.0.0" - domhandler "^4.0.0" - domutils "^2.4.3" + css-what "^5.0.0" + domhandler "^4.2.0" + domutils "^2.6.0" nth-check "^2.0.0" -css-what@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-4.0.0.tgz#35e73761cab2eeb3d3661126b23d7aa0e8432233" - integrity sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A== +css-what@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.0.0.tgz#f0bf4f8bac07582722346ab243f6a35b512cfc47" + integrity sha512-qxyKHQvgKwzwDWC/rGbT821eJalfupxYW2qbSJSAtdSTimsr/MlaGONoNLllaUPZWf8QnbcKM/kPVYUQuEKAFA== cssom@^0.4.4: version "0.4.4" @@ -1841,7 +1849,7 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-serializer@^1.0.1, dom-serializer@~1.2.0: +dom-serializer@^1.0.1: version "1.2.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.2.0.tgz#3433d9136aeb3c627981daa385fc7f32d27c48f1" integrity sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA== @@ -1850,7 +1858,16 @@ dom-serializer@^1.0.1, dom-serializer@~1.2.0: domhandler "^4.0.0" entities "^2.0.0" -domelementtype@^2.0.1, domelementtype@^2.1.0, domelementtype@^2.2.0: +dom-serializer@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.1.tgz#d845a1565d7c041a95e5dab62184ab41e3a519be" + integrity sha512-Pv2ZluG5ife96udGgEDovOOOA5UELkltfJpnIExPrAk1LTvecolUGn6lIaoLh86d83GiB86CjzciMd9BuRB71Q== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.0.0" + entities "^2.0.0" + +domelementtype@^2.0.1, domelementtype@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== @@ -1862,21 +1879,28 @@ domexception@^2.0.1: dependencies: webidl-conversions "^5.0.0" -domhandler@^4.0.0, domhandler@^4.1.0: +domhandler@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.1.0.tgz#c1d8d494d5ec6db22de99e46a149c2a4d23ddd43" integrity sha512-/6/kmsGlMY4Tup/nGVutdrK9yQi4YjWVcVeoQmixpzjOUK1U7pQkvAPHBJeUxOgxF0J8f8lwCJSlCfD0V4CMGQ== dependencies: domelementtype "^2.2.0" -domutils@^2.4.3, domutils@^2.4.4: - version "2.5.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.5.1.tgz#9b8e84b5d9f788499ae77506ea832e9b4f9aa1c0" - integrity sha512-hO1XwHMGAthA/1KL7c83oip/6UWo3FlUNIuWiWKltoiQ5oCOiqths8KknvY2jpOohUoUgnwa/+Rm7UpwpSbY/Q== +domhandler@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.0.tgz#f9768a5f034be60a89a27c2e4d0f74eba0d8b059" + integrity sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA== + dependencies: + domelementtype "^2.2.0" + +domutils@^2.5.2, domutils@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.6.0.tgz#2e15c04185d43fb16ae7057cb76433c6edb938b7" + integrity sha512-y0BezHuy4MDYxh6OvolXYsH+1EMGmFbwv5FKW7ovwMG6zTPWqNPq3WF9ayZssFq+UlKdffGLbOEaghNdaOm1WA== dependencies: dom-serializer "^1.0.1" domelementtype "^2.2.0" - domhandler "^4.1.0" + domhandler "^4.2.0" ecc-jsbn@~0.1.1: version "0.1.2" @@ -1911,11 +1935,6 @@ entities@~2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== -entities@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" - integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== - escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -2225,6 +2244,15 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -2292,13 +2320,13 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -genius-lyrics@^4.2.7: - version "4.2.7" - resolved "https://registry.yarnpkg.com/genius-lyrics/-/genius-lyrics-4.2.7.tgz#e85f65eb2de4ea2c0af1e11b13dda11e0c9744a9" - integrity sha512-laoeF2/P+Ed4uewuG6OeqymKTNdfGuymkCohMHIgr3g2DwziW49USXcEGCog1vnEDCpf2LhznNi3WOeLeSmAww== +genius-lyrics@^4.2.9: + version "4.2.9" + resolved "https://registry.yarnpkg.com/genius-lyrics/-/genius-lyrics-4.2.9.tgz#87d12946589d3e96df4e100c51805aa5046de2c7" + integrity sha512-BQm/gmaXEckLAc/Z9ZsGNWxh4CHudx0G1Key8Rnv0xFcqmrxIvssJIDBgDPJLqB6dpVas3uj6LQng1yMFtxfNA== dependencies: axios "^0.21.1" - node-html-parser "^3.0.4" + cheerio "^1.0.0-rc.9" gensync@^1.0.0-beta.2: version "1.0.0-beta.2" @@ -2458,11 +2486,6 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -he@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - himalaya@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/himalaya/-/himalaya-1.1.0.tgz#31724ae9d35714cd7c6f4be94888953f3604606a" @@ -2475,14 +2498,14 @@ html-encoding-sniffer@^2.0.1: dependencies: whatwg-encoding "^1.0.5" -htmlparser2@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.0.1.tgz#422521231ef6d42e56bd411da8ba40aa36e91446" - integrity sha512-GDKPd+vk4jvSuvCbyuzx/unmXkk090Azec7LovXP8as1Hn8q9p3hbjmDGbUqqhknw0ajwit6LiiWqfiTUPMK7w== +htmlparser2@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" + integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== dependencies: domelementtype "^2.0.1" domhandler "^4.0.0" - domutils "^2.4.4" + domutils "^2.5.2" entities "^2.0.0" http-proxy-agent@^4.0.1: @@ -2724,11 +2747,6 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -iso8601-duration@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/iso8601-duration/-/iso8601-duration-1.3.0.tgz#29d7b69e0574e4acdee50c5e5e09adab4137ba5a" - integrity sha512-K4CiUBzo3YeWk76FuET/dQPH03WE04R94feo5TSKQCXpoXQt9E4yx2CnY737QZnSAI3PI4WlKo/zfqizGx52QQ== - isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" @@ -2844,7 +2862,7 @@ jsdoc@^3.6.3: taffydb "2.6.2" underscore "~1.10.2" -jsdom@^16.5.2: +jsdom@^16.5.3: version "16.5.3" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.5.3.tgz#13a755b3950eb938b4482c407238ddf16f0d2136" integrity sha512-Qj1H+PEvUsOtdPJ056ewXM4UJPCi4hhLA8wpiz9F2YvsRBhuFsXxtrIFAgGBDynQA9isAMGE91PfUYbdMPXuTA== @@ -3014,7 +3032,7 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -m3u8stream@^0.8.0, m3u8stream@^0.8.3: +m3u8stream@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/m3u8stream/-/m3u8stream-0.8.3.tgz#c4624e92b4240eb356d040c4a5e155586cf58108" integrity sha512-0nAcdrF8YJKUkb6PzWdvGftTPyCVWgoiot1AkNVbPKTeIGsWs6DrOjifrJ0Zi8WQfQmD2SuVCjkYIOip12igng== @@ -3230,14 +3248,6 @@ node-fetch@2.6.1, node-fetch@^2.6.0, node-fetch@^2.6.1: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== -node-html-parser@^3.0.4: - version "3.1.5" - resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-3.1.5.tgz#ffb62f2a336b6b634f41f3315487fe446fb9d7b5" - integrity sha512-/XKKdWbSUymlXTjtNBcDlmM7Jp8S/BqGMzLx7r2bd2NMjTXz+ofuLcz0Bl3VT0vTvVzF+N511FNLrZt4HVitXA== - dependencies: - css-select "^3.1.2" - he "1.2.0" - node-releases@^1.1.71: version "1.1.71" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb" @@ -3404,14 +3414,14 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse5-htmlparser2-tree-adapter@^6.0.0: +parse5-htmlparser2-tree-adapter@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== dependencies: parse5 "^6.0.1" -parse5@6.0.1, parse5@^6.0.0, parse5@^6.0.1: +parse5@6.0.1, parse5@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== @@ -3856,14 +3866,6 @@ signal-exit@^3.0.0, signal-exit@^3.0.2: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== -simple-youtube-api@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/simple-youtube-api/-/simple-youtube-api-5.2.1.tgz#d1f6efb941ce404f50ce56e0c5e6bff249fcac6a" - integrity sha512-vmndP9Bkh35tifn2OwY+th2imSsfYtmDqczgdOW5yEARFzvSoR8VSQFsivJnctfV5QHQUL6VrOpNdbmDRLh9Bg== - dependencies: - iso8601-duration "^1.2.0" - node-fetch "^2.6.0" - slash@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" @@ -3917,13 +3919,13 @@ sort-array@^2.0.0: object-get "^2.1.0" typical "^2.6.0" -soundcloud-scraper@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/soundcloud-scraper/-/soundcloud-scraper-4.0.4.tgz#dfd6a45dc6e63fac7d6b31f2ba1d23a199ca4fb6" - integrity sha512-ei3KuPsVZRiq9j2GN580gQwVGZUWMdkmDAANSPm8qweUa4/UnKAnYiUsc/6volZsQiGsnJAP9+8HECDxNcTg6A== +soundcloud-scraper@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/soundcloud-scraper/-/soundcloud-scraper-5.0.0.tgz#3f4e9d9fc9ee79cbaf6cdd64f462ad6803e25ac1" + integrity sha512-8Rt7WbW85AqiFoKkemF5BKsGAThLGnhOi13ztctAI0Y/wso/iuR97yiVXK1eYRc2YqaD8gkpKUAjEY7bOxLxnw== dependencies: - cheerio "^1.0.0-rc.3" - m3u8stream "^0.8.0" + cheerio "^1.0.0-rc.9" + m3u8stream "^0.8.3" node-fetch "^2.6.1" source-map-resolve@^0.5.0: @@ -4262,6 +4264,11 @@ tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" + integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== + tslint-config-prettier@^1.18.0: version "1.18.0" resolved "https://registry.yarnpkg.com/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz#75f140bde947d35d8f0d238e0ebf809d64592c37" @@ -4627,13 +4634,13 @@ yargs@^14.0.0: y18n "^4.0.0" yargs-parser "^15.0.1" -youtube-sr@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/youtube-sr/-/youtube-sr-4.0.6.tgz#e27c8fadb51361a6223ba0552b300f131c903fe9" - integrity sha512-nRrqgWl0xYfMhwLTqjF7G6s+36IHIdOMtZaSrz0Cpk4uSIqoeKEJgLiAZrYIGWGNYtS8/mzZYRzYxaIA/gW1Ig== +youtube-sr@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/youtube-sr/-/youtube-sr-4.0.7.tgz#156137713a5df2d02c0698fd819fca2249f3a9f3" + integrity sha512-s05pA+NgD0iPw97XtRXiy5GaEbYyIVsfArmGO5UfvSO5etrG6DxAs1uKqTFbQH4IGTqMsb82M14iB5ohkOD2Mw== dependencies: + "@types/node-fetch" "^2.5.10" node-fetch "^2.6.1" - simple-youtube-api "^5.2.1" ytdl-core@^4.7.0: version "4.7.0"