cleanup base

This commit is contained in:
Snowflake107 2021-06-10 13:20:49 +05:45
parent fac2662025
commit e7031a29f9
11 changed files with 79 additions and 597 deletions

View file

@ -8,7 +8,7 @@
"lib/"
],
"scripts": {
"test": "yarn build && cd test && node index.js",
"test": "cd test && ts-node index.ts",
"build": "tsc",
"format": "prettier --write \"src/**/*.ts\"",
"lint": "tslint -p tsconfig.json",
@ -71,6 +71,7 @@
"discord.js-docgen": "discordjs/docgen#ts-patch",
"jsdoc-babel": "^0.5.0",
"prettier": "^2.2.1",
"ts-node": "^10.0.0",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0",
"typescipt": "^1.0.0",

View file

@ -1,202 +1 @@
import { EventEmitter } from 'events';
import { Client, Collection, Snowflake, Message, Collector } from 'discord.js';
import Util from './utils/Util';
import Queue from './Structures/Queue';
import Track from './Structures/Track';
import PlayerError from './utils/PlayerError';
import { ExtractorModel } from './Structures/ExtractorModel';
import ytdl from 'discord-ytdl-core';
import { PlayerEvents, PlayerErrorEventCodes } from './utils/Constants';
export class Player extends EventEmitter {
public client: Client;
public queues = new Collection<Snowflake, Queue>();
public Extractors = new Collection<string, ExtractorModel>();
private _cooldownsTimeout = new Collection<string, NodeJS.Timeout>();
private _resultsCollectors = new Collection<string, Collector<Snowflake, Message>>();
constructor(client: Client) {
super();
Object.defineProperty(this, 'client', {
value: client,
enumerable: false
});
Util.alertFFmpeg();
}
public createQueue(message: Message) {
return new Promise<Queue>((resolve) => {
if (this.queues.has(message.guild.id)) return this.queues.get(message.guild.id);
const channel = message.member.voice?.channel;
if (!channel)
return void this.emit(
PlayerEvents.ERROR,
new PlayerError(
'Voice connection is not available in this server!',
PlayerErrorEventCodes.NOT_CONNECTED,
message
)
);
const queue = new Queue(this, message.guild);
void this.queues.set(message.guild.id, queue);
channel
.join()
.then((connection) => {
this.emit(PlayerEvents.CONNECTION_CREATE, message, connection);
queue.voiceConnection = connection;
if (queue.options.setSelfDeaf) connection.voice.setSelfDeaf(true);
this.emit(PlayerEvents.QUEUE_CREATE, message, queue);
resolve(queue);
})
.catch((err) => {
this.queues.delete(message.guild.id);
this.emit(
PlayerEvents.ERROR,
new PlayerError(err.message ?? err, PlayerErrorEventCodes.UNABLE_TO_JOIN, message)
);
});
return queue;
});
}
public getQueue(message: Message) {
return this.queues.get(message.guild.id) ?? null;
}
async play(message: Message, query: string | Track, firstResult?: boolean): Promise<void> {
if (!message) throw new PlayerError('Play function needs message');
if (!query) throw new PlayerError('Play function needs search query as a string or Player.Track object');
if (this._cooldownsTimeout.has(`end_${message.guild.id}`)) {
clearTimeout(this._cooldownsTimeout.get(`end_${message.guild.id}`));
this._cooldownsTimeout.delete(`end_${message.guild.id}`);
}
if (typeof query === 'string') query = query.replace(/<(.+)>/g, '$1');
let track;
const queue = this.getQueue(message);
if (query instanceof Track) track = query;
else {
if (ytdl.validateURL(query)) {
const info = await ytdl.getBasicInfo(query).catch(() => {});
if (!info) return void this.emit(PlayerEvents.NO_RESULTS, message, query);
if (info.videoDetails.isLiveContent && !queue.options.enableLive)
return void this.emit(
PlayerEvents.ERROR,
new PlayerError('Live video is not enabled!', PlayerErrorEventCodes.LIVE_VIDEO, message)
);
const lastThumbnail = info.videoDetails.thumbnails[info.videoDetails.thumbnails.length - 1];
track = new Track(this, {
title: info.videoDetails.title,
description: info.videoDetails.description,
author: info.videoDetails.author.name,
url: info.videoDetails.video_url,
thumbnail: lastThumbnail.url,
duration: Util.buildTimeCode(Util.parseMS(parseInt(info.videoDetails.lengthSeconds) * 1000)),
views: parseInt(info.videoDetails.viewCount),
requestedBy: message.author,
fromPlaylist: false,
source: 'youtube',
live: Boolean(info.videoDetails.isLiveContent)
});
} else {
for (const [_, extractor] of this.Extractors) {
if (extractor.validate(query)) {
const data = await extractor.handle(query);
if (data) {
track = new Track(this, {
title: data.title,
description: data.description,
duration: Util.buildTimeCode(Util.parseMS(data.duration)),
thumbnail: data.thumbnail,
author: data.author,
views: data.views,
engine: data.engine,
source: 'arbitrary',
fromPlaylist: false,
requestedBy: message.author,
url: data.url
});
if (extractor.important) break;
}
}
}
if (!track) track = await this.searchTracks(message, query, firstResult);
}
}
if (track) {
if (queue) {
const q = queue.addTrack(track);
this.emit(PlayerEvents.TRACK_ADD, message, q, q.tracks[q.tracks.length - 1]);
} else {
const q = queue.addTrack(track);
if (q) this.emit(PlayerEvents.TRACK_START, message, q.tracks[0], q);
// todo: start playing
}
}
}
private searchTracks(message: Message, query: string, firstResult?: boolean): Promise<Track> {
return new Promise(async (resolve) => {
let tracks: Track[] = [];
const queryType = Util.getQueryType(query);
switch (queryType) {
default:
tracks = await Util.ytSearch(query, { user: message.author, player: this });
}
if (tracks.length < 1) return void this.emit(PlayerEvents.NO_RESULTS, message, query);
if (firstResult || tracks.length === 1) return resolve(tracks[0]);
const collectorString = `${message.author.id}-${message.channel.id}`;
const currentCollector = this._resultsCollectors.get(collectorString);
if (currentCollector) currentCollector.stop();
const collector = message.channel.createMessageCollector((m) => m.author.id === message.author.id, {
time: 60000
});
this._resultsCollectors.set(collectorString, collector);
this.emit(PlayerEvents.SEARCH_RESULTS, message, query, tracks, collector);
collector.on('collect', ({ content }) => {
if (content === 'cancel') {
collector.stop();
return void this.emit(PlayerEvents.SEARCH_CANCEL, message, query, tracks);
}
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(PlayerEvents.SEARCH_INVALID_RESPONSE, message, query, tracks, content, collector);
}
});
collector.on('end', (_, reason) => {
if (reason === 'time') {
this.emit(PlayerEvents.SEARCH_CANCEL, message, query, tracks);
}
});
});
}
}
export default Player;
export {};

View file

@ -1,70 +1 @@
import { ExtractorModelData } from '../types/types';
class ExtractorModel {
name: string;
private _raw: any;
/**
* Model for raw Discord Player extractors
* @param {String} extractorName Name of the extractor
* @param {Object} data Extractor object
*/
constructor(extractorName: string, data: any) {
/**
* The extractor name
* @type {String}
*/
this.name = extractorName;
Object.defineProperty(this, '_raw', { value: data, configurable: false, writable: false, enumerable: false });
}
/**
* Method to handle requests from `Player.play()`
* @param {String} query Query to handle
* @returns {Promise<ExtractorModelData>}
*/
async handle(query: string): Promise<ExtractorModelData> {
const data = await this._raw.getInfo(query);
if (!data) return null;
return {
title: data.title,
duration: data.duration,
thumbnail: data.thumbnail,
engine: data.engine,
views: data.views,
author: data.author,
description: data.description,
url: data.url
};
}
/**
* Method used by Discord Player to validate query with this extractor
* @param {String} query The query to validate
* @returns {Boolean}
*/
validate(query: string): boolean {
return Boolean(this._raw.validate(query));
}
/**
* The extractor version
* @type {String}
*/
get version(): string {
return this._raw.version ?? '0.0.0';
}
/**
* If player should mark this extractor as important
* @type {Boolean}
*/
get important(): boolean {
return Boolean(this._raw.important);
}
}
export default ExtractorModel;
export { ExtractorModel };
export {};

View file

@ -1,11 +1 @@
import Player from '../Player';
export class Playlist {
player: Player;
constructor(player: Player, data: any) {
Object.defineProperty(this, 'player', { value: player });
}
}
export default Playlist;
export {};

View file

@ -1,42 +1 @@
import { Guild, Message, VoiceConnection } from 'discord.js';
import { Player } from '../Player';
import { PlayerOptions } from '../types/types';
import Track from './Track';
import { PlayerError } from '../utils/PlayerError';
export class Queue {
player: Player;
guild: Guild;
firstMessage: Message;
options: PlayerOptions = {};
tracks: Track[] = [];
voiceConnection: VoiceConnection = null;
constructor(player: Player, guild: Guild) {
Object.defineProperty(this, 'player', { value: player, enumerable: false });
this.guild = guild;
}
get playing() {
return this.tracks[0];
}
async play(message: Message, query: string | Track, firstResult?: boolean) {
return await this.player.play(message, query, firstResult);
}
addTrack(track: Track) {
if (!track || !(track instanceof Track)) throw new PlayerError('No track specified to add to the queue');
this.tracks.push(track);
return this;
}
addTracks(tracks: Track[]) {
this.tracks.push(...tracks);
return this;
}
}
export default Queue;
export {};

View file

@ -1,13 +1 @@
import { Message } from 'discord.js';
import { Player } from '../Player';
export class Track {
readonly player: Player;
readonly message: Message;
constructor(player: Player, data: any) {
Object.defineProperty(this, 'player', { value: player, enumerable: false });
}
}
export default Track;
export {};

View file

@ -1,89 +0,0 @@
import { DiscordGatewayAdapterCreator, DiscordGatewayAdapterLibraryMethods } from '@discordjs/voice';
import {
VoiceChannel,
Snowflake,
Client,
Constants,
WebSocketShard,
Guild,
StageChannel,
Collection
} from 'discord.js';
import { GatewayVoiceServerUpdateDispatchData, GatewayVoiceStateUpdateDispatchData } from 'discord-api-types/v8';
class VoiceAdapter {
public client: Client;
public adapters = new Collection<Snowflake, DiscordGatewayAdapterLibraryMethods>();
public clients = new Set<Client>();
public guilds = new Collection<WebSocketShard, Set<Snowflake>>();
constructor(client: Client) {
this.client = client;
Object.defineProperty(this, 'client', {
enumerable: false,
writable: true,
configurable: true
});
}
trackVoiceState() {
if (this.clients.has(this.client)) return;
this.clients.add(this.client);
this.client.ws.on('VOICE_STATE_UPDATE', (data: GatewayVoiceServerUpdateDispatchData) => {
this.adapters.get(data.guild_id)?.onVoiceServerUpdate(data);
});
this.client.ws.on(Constants.WSEvents.VOICE_STATE_UPDATE, (payload: GatewayVoiceStateUpdateDispatchData) => {
if (payload.guild_id && payload.session_id && payload.user_id === this.client.user?.id) {
this.adapters.get(payload.guild_id)?.onVoiceStateUpdate(payload);
}
});
}
cleanupGuilds(shard: WebSocketShard) {
const guilds = this.guilds.get(shard);
if (guilds) {
for (const guildID of guilds.values()) {
this.adapters.get(guildID)?.destroy();
}
}
}
trackGuild(guild: Guild) {
let guilds = this.guilds.get(guild.shard);
if (!guilds) {
const cleanup = () => this.cleanupGuilds(guild.shard);
guild.shard.on('close', cleanup);
guild.shard.on('destroyed', cleanup);
guilds = new Set();
this.guilds.set(guild.shard, guilds);
}
guilds.add(guild.id);
}
}
export default function createAdapter(channel: VoiceChannel | StageChannel): DiscordGatewayAdapterCreator {
return (methods) => {
const adapter = new VoiceAdapter(channel.client);
adapter.adapters.set(channel.guild.id, methods);
adapter.trackVoiceState();
adapter.trackGuild(channel.guild);
return {
sendPayload(data) {
if (channel.guild.shard.status === Constants.Status.READY) {
channel.guild.shard.send(data);
return true;
}
return false;
},
destroy() {
return adapter.adapters.delete(channel.guild.id);
}
};
};
}

View file

@ -1 +0,0 @@
class VoiceSubscriptionManager {}

View file

@ -1,9 +1 @@
export { AudioFilters } from './utils/AudioFilters';
export * as Constants from './utils/Constants';
export { ExtractorModel } from './Structures/ExtractorModel';
export { Player } from './Player';
export { Util } from './utils/Util';
export { Track } from './Structures/Track';
export { Queue } from './Structures/Queue';
export * from './types/types';
export { PlayerError } from './utils/PlayerError';
export {};

View file

@ -1,158 +1 @@
import { downloadOptions } from 'ytdl-core';
import { User } from 'discord.js';
import { Readable, Duplex } from 'stream';
export interface PlayerOptions {
leaveOnEnd?: boolean;
leaveOnEndCooldown?: number;
leaveOnStop?: boolean;
leaveOnEmpty?: boolean;
leaveOnEmptyCooldown?: number;
setSelfDeaf?: boolean;
enableLive?: boolean;
ytdlDownloadOptions?: downloadOptions;
useSafeSearch?: boolean;
disableAutoRegister?: boolean;
}
export type FiltersName = keyof QueueFilters;
export type TrackSource = 'soundcloud' | 'youtube' | 'arbitrary';
export interface TrackData {
title: string;
description: string;
author: string;
url: string;
thumbnail: string;
duration: string;
views: number;
requestedBy: User;
fromPlaylist: boolean;
source?: TrackSource;
engine?: any;
live?: boolean;
}
export type QueueFilters = {
bassboost?: boolean;
'8D'?: boolean;
vaporwave?: boolean;
nightcore?: boolean;
phaser?: boolean;
tremolo?: boolean;
vibrato?: boolean;
reverse?: boolean;
treble?: boolean;
normalizer?: boolean;
surrounding?: boolean;
pulsator?: boolean;
subboost?: boolean;
karaoke?: boolean;
flanger?: boolean;
gate?: boolean;
haas?: boolean;
mcompand?: boolean;
mono?: boolean;
mstlr?: boolean;
mstrr?: boolean;
compressor?: boolean;
expander?: boolean;
softlimiter?: boolean;
chorus?: boolean;
chorus2d?: boolean;
chorus3d?: boolean;
fadein?: boolean;
};
export type QueryType =
| 'soundcloud_track'
| 'soundcloud_playlist'
| 'spotify_song'
| 'spotify_album'
| 'spotify_playlist'
| 'youtube_video'
| 'youtube_playlist'
| 'vimeo'
| 'facebook'
| 'reverbnation'
| 'attachment'
| 'youtube_search';
export interface ExtractorModelData {
title: string;
duration: number;
thumbnail: string;
engine: string | Readable | Duplex;
views: number;
author: string;
description: string;
url: string;
version?: string;
important?: boolean;
}
export interface PlayerProgressbarOptions {
timecodes?: boolean;
queue?: boolean;
length?: number;
line?: string;
indicator?: string;
}
export interface LyricsData {
title: string;
id: number;
thumbnail: string;
image: string;
url: string;
artist: {
name: string;
id: number;
url: string;
image: string;
};
lyrics?: string;
}
export interface PlayerStats {
uptime: number;
connections: number;
users: number;
queues: number;
extractors: number;
versions: {
ffmpeg: string;
node: string;
v8: string;
};
system: {
arch: string;
platform:
| 'aix'
| 'android'
| 'darwin'
| 'freebsd'
| 'linux'
| 'openbsd'
| 'sunos'
| 'win32'
| 'cygwin'
| 'netbsd';
cpu: number;
memory: {
total: string;
usage: string;
rss: string;
arrayBuffers: string;
};
uptime: number;
};
}
export interface TimeData {
days: number;
hours: number;
minutes: number;
seconds: number;
}
export {};

View file

@ -994,6 +994,26 @@
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
"@tsconfig/node10@^1.0.7":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9"
integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==
"@tsconfig/node12@^1.0.7":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.8.tgz#a883d62f049a64fea1e56a6bbe66828d11c6241b"
integrity sha512-LM6XwBhjZRls1qJGpiM/It09SntEwe9M0riXRfQ9s6XlJQG0JPGl92ET18LtGeYh/GuOtafIXqwZeqLOd0FNFQ==
"@tsconfig/node14@^1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2"
integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==
"@tsconfig/node16@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.1.tgz#a6ca6a9a0ff366af433f42f5f0e124794ff6b8f1"
integrity sha512-FTgBI767POY/lKNDNbIzgAX6miIDBs6NTCbdlDb8TrWovHsSvaVIZDlTqym29C6UqhzwcJx4CYr+AlrMywA0cA==
"@types/node@*":
version "15.12.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.2.tgz#1f2b42c4be7156ff4a6f914b2fb03d05fa84e38d"
@ -1150,6 +1170,11 @@ are-we-there-yet@~1.1.2:
delegates "^1.0.0"
readable-stream "^2.0.6"
arg@^4.1.0:
version "4.1.3"
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
@ -1349,6 +1374,11 @@ browserslist@^4.16.6:
escalade "^3.1.1"
node-releases "^1.1.71"
buffer-from@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
builtin-modules@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
@ -1655,6 +1685,11 @@ core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
create-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
cross-fetch@~3.1.4:
version "3.1.4"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39"
@ -2964,6 +2999,11 @@ make-dir@^3.1.0:
dependencies:
semver "^6.0.0"
make-error@^1.1.1:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
map-cache@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
@ -3788,6 +3828,14 @@ source-map-resolve@^0.5.0:
source-map-url "^0.4.0"
urix "^0.1.0"
source-map-support@^0.5.17:
version "0.5.19"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map-url@^0.4.0:
version "0.4.1"
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56"
@ -3798,7 +3846,7 @@ source-map@^0.5.0, source-map@^0.5.6:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
source-map@^0.6.1, source-map@~0.6.1:
source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
@ -4085,6 +4133,22 @@ tr46@^2.1.0:
dependencies:
punycode "^2.1.1"
ts-node@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.0.0.tgz#05f10b9a716b0b624129ad44f0ea05dac84ba3be"
integrity sha512-ROWeOIUvfFbPZkoDis0L/55Fk+6gFQNZwwKPLinacRl6tsxstTF1DbAcLKkovwnpKMVvOMHP1TIbnwXwtLg1gg==
dependencies:
"@tsconfig/node10" "^1.0.7"
"@tsconfig/node12" "^1.0.7"
"@tsconfig/node14" "^1.0.0"
"@tsconfig/node16" "^1.0.1"
arg "^4.1.0"
create-require "^1.1.0"
diff "^4.0.1"
make-error "^1.1.1"
source-map-support "^0.5.17"
yn "3.1.1"
tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
@ -4439,6 +4503,11 @@ yargs@^14.0.0:
y18n "^4.0.0"
yargs-parser "^15.0.1"
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
youtube-sr@^4.1.4:
version "4.1.4"
resolved "https://registry.yarnpkg.com/youtube-sr/-/youtube-sr-4.1.4.tgz#5ea646812264a951df7b1f6094f9f7406001dde6"