From 17ba78f7b7d78c638ab00b9d4af79110130b0bcd Mon Sep 17 00:00:00 2001 From: Max Isom Date: Thu, 12 Mar 2020 22:41:26 -0500 Subject: [PATCH] Use IoC, impliment queue --- package.json | 21 +- src/bot.ts | 86 +++++++ src/commands/config.ts | 19 +- src/commands/index.ts | 7 + src/commands/play.ts | 212 ++++++++++++++-- src/commands/queue.ts | 22 ++ src/index.ts | 75 +----- src/interfaces.ts | 7 - src/inversify.config.ts | 54 +++++ src/packages.d.ts | 1 + src/services/player.ts | 88 +++++++ src/services/queue.ts | 89 +++++++ src/types.ts | 20 ++ src/utils/config.ts | 3 + src/utils/get-youtube-stream.ts | 29 ++- src/utils/loading-message.ts | 67 ++++++ yarn.lock | 412 +++++++++++++++++++++++++++++--- 17 files changed, 1081 insertions(+), 131 deletions(-) create mode 100644 src/bot.ts create mode 100644 src/commands/index.ts create mode 100644 src/commands/queue.ts delete mode 100644 src/interfaces.ts create mode 100644 src/inversify.config.ts create mode 100644 src/services/player.ts create mode 100644 src/services/queue.ts create mode 100644 src/types.ts create mode 100644 src/utils/loading-message.ts diff --git a/package.json b/package.json index 7017541..05f36a2 100644 --- a/package.json +++ b/package.json @@ -25,11 +25,12 @@ }, "devDependencies": { "@types/bluebird": "^3.5.30", - "@types/node": "^13.9.0", + "@types/node": "^13.9.1", + "@types/spotify-web-api-node": "^4.0.1", "@types/validator": "^12.0.1", "@types/ws": "^7.2.2", - "@typescript-eslint/eslint-plugin": "^2.22.0", - "@typescript-eslint/parser": "^2.22.0", + "@typescript-eslint/eslint-plugin": "^2.23.0", + "@typescript-eslint/parser": "^2.23.0", "eslint": "^6.8.0", "eslint-config-xo": "^0.29.1", "eslint-config-xo-typescript": "^0.26.0", @@ -62,14 +63,24 @@ }, "dependencies": { "@discordjs/opus": "^0.1.0", - "discord.js": "^12.0.1", + "delay": "^4.3.0", + "discord.js": "^12.0.2", "dotenv": "^8.2.0", + "got": "^10.6.0", "hasha": "^5.2.0", + "inversify": "^5.0.1", + "iso8601-duration": "^1.2.0", "make-dir": "^3.0.2", "node-emoji": "^1.10.0", + "p-limit": "^2.2.2", + "prism-media": "^1.2.1", "sequelize": "^5.21.5", "sequelize-typescript": "^1.1.0", + "spotify-uri": "^2.0.0", + "spotify-web-api-node": "^4.0.0", "sqlite3": "^4.1.1", - "ytdl-core": "^2.0.0" + "youtube.ts": "^0.1.0", + "ytdl-core": "^2.0.0", + "ytsr": "^0.1.11" } } diff --git a/src/bot.ts b/src/bot.ts new file mode 100644 index 0000000..2b9f07d --- /dev/null +++ b/src/bot.ts @@ -0,0 +1,86 @@ +import makeDir from 'make-dir'; +import {Client, Message, Collection} from 'discord.js'; +import {inject, injectable} from 'inversify'; +import {TYPES} from './types'; +import {Settings} from './models'; +import {sequelize} from './utils/db'; +import handleGuildCreate from './events/guild-create'; +import container from './inversify.config'; +import Command from './commands'; + +@injectable() +export default class { + private readonly client: Client; + private readonly token: string; + private readonly clientId: string; + private readonly dataDir: string; + private readonly cacheDir: string; + private readonly commands!: Collection; + + constructor(@inject(TYPES.Client) client: Client, @inject(TYPES.Config.DISCORD_TOKEN) token: string, @inject(TYPES.Config.DISCORD_CLIENT_ID) clientId: string, @inject(TYPES.Config.DATA_DIR) dataDir: string, @inject(TYPES.Config.CACHE_DIR) cacheDir: string) { + this.client = client; + this.token = token; + this.clientId = clientId; + this.dataDir = dataDir; + this.cacheDir = cacheDir; + this.commands = new Collection(); + } + + public async listen(): Promise { + // Load in commands + container.getAll(TYPES.Command).forEach(command => { + this.commands.set(command.name, command); + }); + + this.client.on('message', async (msg: Message) => { + // Get guild settings + if (!msg.guild) { + return; + } + + const settings = await Settings.findByPk(msg.guild.id); + + if (!settings) { + // Got into a bad state, send owner welcome message + return this.client.emit('guildCreate', msg.guild); + } + + const {prefix, channel} = settings; + + if (!msg.content.startsWith(prefix) || msg.author.bot || msg.channel.id !== channel) { + return; + } + + const args = msg.content.slice(prefix.length).split(/ +/); + const command = args.shift()!.toLowerCase(); + + if (!this.commands.has(command)) { + return; + } + + try { + const handler = this.commands.get(command); + + handler!.execute(msg, args); + } catch (error) { + console.error(error); + msg.reply('there was an error trying to execute that command!'); + } + }); + + this.client.on('ready', async () => { + // Create directory if necessary + await makeDir(this.dataDir); + await makeDir(this.cacheDir); + + await sequelize.sync({}); + + console.log(`Ready! Invite the bot with https://discordapp.com/oauth2/authorize?client_id=${this.clientId}&scope=bot`); + }); + + // Register event handlers + this.client.on('guildCreate', handleGuildCreate); + + return this.client.login(this.token); + } +} diff --git a/src/commands/config.ts b/src/commands/config.ts index 80d5727..597578d 100644 --- a/src/commands/config.ts +++ b/src/commands/config.ts @@ -1,11 +1,14 @@ -import {TextChannel} from 'discord.js'; -import {CommandHandler} from '../interfaces'; +import {TextChannel, Message} from 'discord.js'; +import {injectable} from 'inversify'; import {Settings} from '../models'; +import Command from '.'; -const config: CommandHandler = { - name: 'config', - description: 'Change various bot settings.', - execute: async (msg, args) => { +@injectable() +export default class implements Command { + public name = 'config'; + public description = 'changes various bot settings'; + + public async execute(msg: Message, args: string []): Promise { if (args.length === 0) { // Show current settings const settings = await Settings.findByPk(msg.guild!.id); @@ -58,6 +61,4 @@ const config: CommandHandler = { await msg.channel.send('🚫 I\'ve never met this setting in my life'); } } -}; - -export default config; +} diff --git a/src/commands/index.ts b/src/commands/index.ts new file mode 100644 index 0000000..1a6d686 --- /dev/null +++ b/src/commands/index.ts @@ -0,0 +1,7 @@ +import {Message} from 'discord.js'; + +export default interface Command { + name: string; + description: string; + execute: (msg: Message, args: string[]) => Promise; +} diff --git a/src/commands/play.ts b/src/commands/play.ts index cf23d60..5a96bb6 100644 --- a/src/commands/play.ts +++ b/src/commands/play.ts @@ -1,21 +1,205 @@ -import {CommandHandler} from '../interfaces'; +import {TextChannel, Message} from 'discord.js'; +import YouTube from 'youtube.ts'; +import Spotify from 'spotify-web-api-node'; +import {URL} from 'url'; +import ytsr from 'ytsr'; +import pLimit from 'p-limit'; +import spotifyURI from 'spotify-uri'; +import got from 'got'; +import {parse, toSeconds} from 'iso8601-duration'; +import {TYPES} from '../types'; +import {inject, injectable} from 'inversify'; +import Queue, {QueuedSong, QueuedPlaylist} from '../services/queue'; +import Player from '../services/player'; import {getMostPopularVoiceChannel} from '../utils/channels'; -import getYouTubeStream from '../utils/get-youtube-stream'; +import LoadingMessage from '../utils/loading-message'; +import Command from '.'; -const play: CommandHandler = { - name: 'play', - description: 'plays a song', - execute: async (msg, args) => { - const url = args[0]; +@injectable() +export default class implements Command { + public name = 'play'; + public description = 'plays a song'; + private readonly queue: Queue; + private readonly player: Player; + private readonly youtube: YouTube; + private readonly youtubeKey: string; + private readonly spotify: Spotify; + + constructor(@inject(TYPES.Services.Queue) queue: Queue, @inject(TYPES.Services.Player) player: Player, @inject(TYPES.Lib.YouTube) youtube: YouTube, @inject(TYPES.Config.YOUTUBE_API_KEY) youtubeKey: string, @inject(TYPES.Lib.Spotify) spotify: Spotify) { + this.queue = queue; + this.player = player; + this.youtube = youtube; + this.youtubeKey = youtubeKey; + this.spotify = spotify; + } + + public async execute(msg: Message, args: string []): Promise { + const newSongs: QueuedSong[] = []; + + const res = new LoadingMessage(msg.channel as TextChannel, 'hold on a sec'); + await res.start(); + + const addSingleSong = async (source: string): Promise => { + const videoDetails = await this.youtube.videos.get(source); + + newSongs.push({title: videoDetails.snippet.title, length: toSeconds(parse(videoDetails.contentDetails.duration)), url: videoDetails.id, playlist: null}); + }; + + // Test if it's a complete URL + try { + const url = new URL(args[0]); + + const YOUTUBE_HOSTS = ['www.youtube.com', 'youtu.be', 'youtube.com']; + + if (YOUTUBE_HOSTS.includes(url.host)) { + // YouTube source + if (url.searchParams.get('list')) { + // YouTube playlist + const playlist = await this.youtube.playlists.get(url.searchParams.get('list') as string); + const {items} = await this.youtube.playlists.items(url.searchParams.get('list') as string, {maxResults: '50'}); + + // Unfortunately, package doesn't provide a method for this + const res: any = await got('https://www.googleapis.com/youtube/v3/videos', {searchParams: { + part: 'contentDetails', + id: items.map(item => item.contentDetails.videoId).join(','), + key: this.youtubeKey + }}).json(); + + const queuedPlaylist = {title: playlist.snippet.title, source: playlist.id}; + + items.forEach(video => { + const length = toSeconds(parse(res.items.find((i: any) => i.id === video.contentDetails.videoId).contentDetails.duration)); + + newSongs.push({title: video.snippet.title, length, url: video.contentDetails.videoId, playlist: queuedPlaylist}); + }); + } else { + // Single video + try { + await addSingleSong(url.href); + } catch (error) { + await res.stop('that doesn\'t exist'); + return; + } + } + } else if (url.protocol === 'spotify:' || url.host === 'open.spotify.com') { + // Spotify source + const parsed = spotifyURI.parse(args[0]); + + const tracks: SpotifyApi.TrackObjectSimplified[] = []; + + let playlist: QueuedPlaylist | null = null; + + switch (parsed.type) { + case 'album': { + const uri = parsed as spotifyURI.Album; + + const [{body: album}, {body: {items}}] = await Promise.all([this.spotify.getAlbum(uri.id), this.spotify.getAlbumTracks(uri.id, {limit: 50})]); + + tracks.push(...items); + + playlist = {title: album.name, source: album.href}; + break; + } + + case 'playlist': { + const uri = parsed as spotifyURI.Playlist; + + let [{body: playlistResponse}, {body: tracksResponse}] = await Promise.all([this.spotify.getPlaylist(uri.id), this.spotify.getPlaylistTracks(uri.id, {limit: 1})]); + + playlist = {title: playlistResponse.name, source: playlistResponse.href}; + + tracks.push(...tracksResponse.items.map(playlistItem => playlistItem.track)); + + while (tracksResponse.next) { + // eslint-disable-next-line no-await-in-loop + ({body: tracksResponse} = await this.spotify.getPlaylistTracks(uri.id, { + limit: parseInt(new URL(tracksResponse.next).searchParams.get('limit') ?? '1', 10), + offset: parseInt(new URL(tracksResponse.next).searchParams.get('offset') ?? '0', 10) + })); + + tracks.push(...tracksResponse.items.map(playlistItem => playlistItem.track)); + } + + break; + } + + case 'track': { + const uri = parsed as spotifyURI.Track; + + const {body} = await this.spotify.getTrack(uri.id); + + tracks.push(body); + break; + } + + case 'artist': { + await res.stop('ope, can\'t add a whole artist'); + return; + } + + default: { + await res.stop('huh?'); + return; + } + } + + // Search YouTube for each track + const searchForTrack = async (track: any): Promise => { + try { + const {items: [video]} = await ytsr(`${track.name as string} ${track.artists[0].name as string} offical`, {limit: 1}); + + return {title: video.title, length: track.duration_ms / 1000, url: video.link, playlist}; + } catch (_) { + // TODO: handle error + return null; + } + }; + + // Limit concurrency so hopefully we don't get banned + const limit = pLimit(3); + let songs = await Promise.all(tracks.map(async track => limit(async () => searchForTrack(track)))); + + // Get rid of null values + songs = songs.reduce((accum: QueuedSong[], song) => { + if (song) { + accum.push(song); + } + + return accum; + }, []); + + newSongs.push(...(songs as QueuedSong[])); + } + } catch (_) { + // Not a URL, must search YouTube + const query = args.join(' '); + + try { + const {items: [video]} = await this.youtube.videos.search({q: query, maxResults: 1, type: 'video'}); + + await addSingleSong(video.id.videoId); + } catch (_) { + await res.stop('that doesn\'t exist'); + return; + } + } + + if (newSongs.length === 0) { + // TODO: better response + await res.stop('huh?'); + return; + } + + newSongs.forEach(song => this.queue.add(msg.guild!.id, song)); + + // TODO: better response + await res.stop('song(s) queued'); const channel = getMostPopularVoiceChannel(msg.guild!); - const conn = await channel.join(); + // TODO: don't connect if already connected. + await this.player.connect(msg.guild!.id, channel); - const stream = await getYouTubeStream(url); - - conn.play(stream, {type: 'webm/opus'}); + await this.player.play(msg.guild!.id); } -}; - -export default play; +} diff --git a/src/commands/queue.ts b/src/commands/queue.ts new file mode 100644 index 0000000..b3a88d9 --- /dev/null +++ b/src/commands/queue.ts @@ -0,0 +1,22 @@ +import {Message} from 'discord.js'; +import {TYPES} from '../types'; +import {inject, injectable} from 'inversify'; +import Queue from '../services/queue'; +import Command from '.'; + +@injectable() +export default class implements Command { + public name = 'queue'; + public description = 'shows current queue'; + private readonly queue: Queue; + + constructor(@inject(TYPES.Services.Queue) queue: Queue) { + this.queue = queue; + } + + public async execute(msg: Message, _: string []): Promise { + const queue = this.queue.get(msg.guild!.id); + + await msg.channel.send('`' + JSON.stringify(queue.slice(0, 10)) + '`'); + } +} diff --git a/src/index.ts b/src/index.ts index b4c760f..c4ce13e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,68 +1,15 @@ -import fs from 'fs'; -import path from 'path'; -import makeDir from 'make-dir'; -import Discord from 'discord.js'; -import {DISCORD_TOKEN, DISCORD_CLIENT_ID, DATA_DIR, CACHE_DIR} from './utils/config'; -import {Settings} from './models'; -import {sequelize} from './utils/db'; -import {CommandHandler} from './interfaces'; -import handleGuildCreate from './events/guild-create'; +import container from './inversify.config'; +import Spotify from 'spotify-web-api-node'; +import {TYPES} from './types'; +import Bot from './bot'; -const client = new Discord.Client(); -const commands = new Discord.Collection(); +let bot = container.get(TYPES.Bot); +const spotify = container.get(TYPES.Lib.Spotify); -// Load in commands -const commandFiles = fs.readdirSync(path.join(__dirname, 'commands')).filter(file => file.endsWith('.js')); +(async () => { + const auth = await spotify.clientCredentialsGrant(); -for (const file of commandFiles) { - const command = require(`./commands/${file}`).default; + spotify.setAccessToken(auth.body.access_token); - commands.set(command.name, command); -} - -// Generic message handler -client.on('message', async (msg: Discord.Message) => { - // Get guild settings - const settings = await Settings.findByPk(msg.guild!.id); - - if (!settings) { - // Got into a bad state, send owner welcome message - return client.emit('guildCreate', msg.guild); - } - - const {prefix, channel} = settings; - - if (!msg.content.startsWith(prefix) || msg.author.bot || msg.channel.id !== channel) { - return; - } - - const args = msg.content.slice(prefix.length).split(/ +/); - const command = args.shift()!.toLowerCase(); - - if (!commands.has(command)) { - return; - } - - try { - const handler = commands.get(command) as CommandHandler; - - handler.execute(msg, args); - } catch (error) { - console.error(error); - msg.reply('there was an error trying to execute that command!'); - } -}); - -client.on('ready', async () => { - // Create directory if necessary - await makeDir(DATA_DIR); - await makeDir(CACHE_DIR); - - await sequelize.sync({}); - - console.log(`Ready! Invite the bot with https://discordapp.com/oauth2/authorize?client_id=${DISCORD_CLIENT_ID}&scope=bot`); -}); - -client.on('guildCreate', handleGuildCreate); - -client.login(DISCORD_TOKEN); + bot.listen(); +})(); diff --git a/src/interfaces.ts b/src/interfaces.ts deleted file mode 100644 index 6909d47..0000000 --- a/src/interfaces.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {Message} from 'discord.js'; - -export interface CommandHandler { - name: string; - description: string; - execute: (msg: Message, args: string[]) => void; -} diff --git a/src/inversify.config.ts b/src/inversify.config.ts new file mode 100644 index 0000000..a042748 --- /dev/null +++ b/src/inversify.config.ts @@ -0,0 +1,54 @@ +import 'reflect-metadata'; +import {Container} from 'inversify'; +import {TYPES} from './types'; +import Bot from './bot'; +import {Client} from 'discord.js'; +import YouTube from 'youtube.ts'; +import Spotify from 'spotify-web-api-node'; +import { + DISCORD_TOKEN, + DISCORD_CLIENT_ID, + YOUTUBE_API_KEY, + SPOTIFY_CLIENT_ID, + SPOTIFY_CLIENT_SECRET, + DATA_DIR, + CACHE_DIR +} from './utils/config'; + +// Services +import Queue from './services/queue'; +import Player from './services/player'; + +// Comands +import Command from './commands'; +import Config from './commands/config'; +import Play from './commands/play'; +import QueueCommad from './commands/queue'; + +let container = new Container(); + +// Bot +container.bind(TYPES.Bot).to(Bot).inSingletonScope(); +container.bind(TYPES.Client).toConstantValue(new Client()); + +// Services +container.bind(TYPES.Services.Player).to(Player).inSingletonScope(); +container.bind(TYPES.Services.Queue).to(Queue).inSingletonScope(); + +// Commands +container.bind(TYPES.Command).to(Config).inSingletonScope(); +container.bind(TYPES.Command).to(Play).inSingletonScope(); +container.bind(TYPES.Command).to(QueueCommad).inSingletonScope(); + +// Config values +container.bind(TYPES.Config.DISCORD_TOKEN).toConstantValue(DISCORD_TOKEN); +container.bind(TYPES.Config.DISCORD_CLIENT_ID).toConstantValue(DISCORD_CLIENT_ID); +container.bind(TYPES.Config.YOUTUBE_API_KEY).toConstantValue(YOUTUBE_API_KEY); +container.bind(TYPES.Config.DATA_DIR).toConstantValue(DATA_DIR); +container.bind(TYPES.Config.CACHE_DIR).toConstantValue(CACHE_DIR); + +// Static libraries +container.bind(TYPES.Lib.YouTube).toConstantValue(new YouTube(YOUTUBE_API_KEY)); +container.bind(TYPES.Lib.Spotify).toConstantValue(new Spotify({clientId: SPOTIFY_CLIENT_ID, clientSecret: SPOTIFY_CLIENT_SECRET})); + +export default container; diff --git a/src/packages.d.ts b/src/packages.d.ts index 029763f..6364cbd 100644 --- a/src/packages.d.ts +++ b/src/packages.d.ts @@ -1 +1,2 @@ declare module 'node-emoji'; +declare module 'ytsr'; diff --git a/src/services/player.ts b/src/services/player.ts new file mode 100644 index 0000000..0fd6f62 --- /dev/null +++ b/src/services/player.ts @@ -0,0 +1,88 @@ +import {inject, injectable} from 'inversify'; +import {VoiceConnection, VoiceChannel} from 'discord.js'; +import {TYPES} from '../types'; +import Queue from './queue'; +import getYouTubeStream from '../utils/get-youtube-stream'; + +export enum Status { + Playing, + Paused, + Disconnected +} + +export interface GuildPlayer { + status: Status; + voiceConnection: VoiceConnection | null; +} + +@injectable() +export default class { + private readonly guildPlayers = new Map(); + private readonly queue: Queue; + + constructor(@inject(TYPES.Services.Queue) queue: Queue) { + this.queue = queue; + } + + async connect(guildId: string, channel: VoiceChannel): Promise { + this.initGuild(guildId); + + const guildPlayer = this.guildPlayers.get(guildId); + + const conn = await channel.join(); + + guildPlayer!.voiceConnection = conn; + + this.guildPlayers.set(guildId, guildPlayer!); + } + + disconnect(guildId: string): void { + this.initGuild(guildId); + + const guildPlayer = this.guildPlayers.get(guildId); + + if (guildPlayer?.voiceConnection) { + guildPlayer.voiceConnection.disconnect(); + } + } + + async play(guildId: string): Promise { + const guildPlayer = this.get(guildId); + if (guildPlayer.voiceConnection === null) { + throw new Error('Not connected to a voice channel.'); + } + + if (guildPlayer.status === Status.Playing) { + // Already playing, return + return; + } + + const songs = this.queue.get(guildId); + + if (songs.length === 0) { + throw new Error('Queue empty.'); + } + + const song = songs[0]; + + const stream = await getYouTubeStream(song.url); + + this.get(guildId).voiceConnection!.play(stream, {type: 'webm/opus'}); + + guildPlayer.status = Status.Playing; + + this.guildPlayers.set(guildId, guildPlayer); + } + + get(guildId: string): GuildPlayer { + this.initGuild(guildId); + + return this.guildPlayers.get(guildId) as GuildPlayer; + } + + private initGuild(guildId: string): void { + if (!this.guildPlayers.get(guildId)) { + this.guildPlayers.set(guildId, {status: Status.Disconnected, voiceConnection: null}); + } + } +} diff --git a/src/services/queue.ts b/src/services/queue.ts new file mode 100644 index 0000000..0e0fd07 --- /dev/null +++ b/src/services/queue.ts @@ -0,0 +1,89 @@ +import {injectable} from 'inversify'; + +export interface QueuedPlaylist { + title: string; + source: string; +} + +export interface QueuedSong { + title: string; + url: string; + length: number; + playlist: QueuedPlaylist | null; +} + +@injectable() +export default class { + private readonly guildQueues = new Map(); + private readonly queuePositions = new Map(); + + forward(guildId: string): void { + const currentPosition = this.queuePositions.get(guildId); + + if (currentPosition && currentPosition + 1 <= this.size(guildId)) { + this.queuePositions.set(guildId, currentPosition + 1); + } else { + throw new Error('No songs in queue to forward to.'); + } + } + + back(guildId: string): void { + const currentPosition = this.queuePositions.get(guildId); + + if (currentPosition && currentPosition - 1 >= 0) { + this.queuePositions.set(guildId, currentPosition - 1); + } else { + throw new Error('No songs in queue to go back to.'); + } + } + + get(guildId: string): QueuedSong[] { + const currentPosition = this.queuePositions.get(guildId); + + if (currentPosition === undefined) { + return []; + } + + const guildQueue = this.guildQueues.get(guildId); + + if (!guildQueue) { + throw new Error('Bad state. Queue for guild exists but position does not.'); + } + + return guildQueue.slice(currentPosition); + } + + add(guildId: string, song: QueuedSong): void { + if (!this.guildQueues.get(guildId)) { + this.guildQueues.set(guildId, []); + this.queuePositions.set(guildId, 0); + } + + if (song.playlist) { + // Add to end of queue + this.guildQueues.set(guildId, [...this.guildQueues.get(guildId)!, song]); + } else if (this.guildQueues.get(guildId)!.length === 0) { + // Queue is currently empty + this.guildQueues.set(guildId, [song]); + } else { + // Not from playlist, add immediately + let insertAt = 0; + + // Loop until playlist song + this.guildQueues.get(guildId)!.some(song => { + if (song.playlist) { + return true; + } + + insertAt++; + return false; + }); + + this.guildQueues.set(guildId, [...this.guildQueues.get(guildId)!.slice(0, insertAt), song, ...this.guildQueues.get(guildId)!.slice(insertAt)]); + } + } + + size(guildId: string): number { + return this.get(guildId).length; + } +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..6a9a8b4 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,20 @@ +export const TYPES = { + Bot: Symbol('Bot'), + Client: Symbol('Client'), + Config: { + DISCORD_TOKEN: Symbol('DISCORD_TOKEN'), + DISCORD_CLIENT_ID: Symbol('DISCORD_CLIENT_ID'), + YOUTUBE_API_KEY: Symbol('YOUTUBE_API_KEY'), + DATA_DIR: Symbol('DATA_DIR'), + CACHE_DIR: Symbol('CACHE_DIR') + }, + Command: Symbol('Command'), + Services: { + Player: Symbol('Player'), + Queue: Symbol('Queue') + }, + Lib: { + YouTube: Symbol('YouTube'), + Spotify: Symbol('Spotify') + } +}; diff --git a/src/utils/config.ts b/src/utils/config.ts index 0b3b33d..fe41649 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -4,5 +4,8 @@ dotenv.config(); export const DISCORD_TOKEN: string = process.env.DISCORD_TOKEN ? process.env.DISCORD_TOKEN : ''; export const DISCORD_CLIENT_ID: string = process.env.DISCORD_CLIENT_ID ? process.env.DISCORD_CLIENT_ID : ''; +export const YOUTUBE_API_KEY: string = process.env.YOUTUBE_API_KEY ? process.env.YOUTUBE_API_KEY : ''; +export const SPOTIFY_CLIENT_ID: string = process.env.SPOTIFY_CLIENT_ID ? process.env.SPOTIFY_CLIENT_ID : ''; +export const SPOTIFY_CLIENT_SECRET: string = process.env.SPOTIFY_CLIENT_SECRET ? process.env.SPOTIFY_CLIENT_SECRET : ''; export const DATA_DIR = path.resolve(process.env.DATA_DIR ? process.env.DATA_DIR : './data'); export const CACHE_DIR = path.join(DATA_DIR, 'cache'); diff --git a/src/utils/get-youtube-stream.ts b/src/utils/get-youtube-stream.ts index 41f687c..1360dce 100644 --- a/src/utils/get-youtube-stream.ts +++ b/src/utils/get-youtube-stream.ts @@ -3,6 +3,7 @@ import {Readable, PassThrough} from 'stream'; import path from 'path'; import hasha from 'hasha'; import ytdl from 'ytdl-core'; +import prism from 'prism-media'; import {CACHE_DIR} from './config'; const nextBestFormat = (formats: ytdl.videoFormat[]): ytdl.videoFormat => { @@ -24,9 +25,11 @@ export default async (url: string): Promise => { const filter = (format: ytdl.videoFormat): boolean => format.codecs === 'opus' && format.container === 'webm' && format.audioSampleRate !== undefined && parseInt(format.audioSampleRate, 10) === 48000; let format = formats.find(filter); + let canDirectPlay = true; if (!format) { format = nextBestFormat(info.formats); + canDirectPlay = false; } try { @@ -46,6 +49,30 @@ export default async (url: string): Promise => { await fs.rename(cacheTempPath, cachedPath); }); - return ytdl.downloadFromInfo(info, {format}).pipe(pass); + if (canDirectPlay) { + return ytdl.downloadFromInfo(info, {format}).pipe(pass); + } + + const transcoder = new prism.FFmpeg({ + args: [ + '-reconnect', + '1', + '-reconnect_streamed', + '1', + '-reconnect_delay_max', + '5', + '-i', + format.url, + '-loglevel', + 'verbose', + '-vn', + '-acodec', + 'libopus', + '-f', + 'webm' + ] + }); + + return transcoder.pipe(pass); } }; diff --git a/src/utils/loading-message.ts b/src/utils/loading-message.ts new file mode 100644 index 0000000..3b3d138 --- /dev/null +++ b/src/utils/loading-message.ts @@ -0,0 +1,67 @@ +import {TextChannel, Message} from 'discord.js'; +import delay from 'delay'; + +export default class { + private readonly channel: TextChannel; + private readonly text: string; + private msg!: Message; + private isStopped: boolean = false; + + constructor(channel: TextChannel, text: string) { + this.channel = channel; + this.text = text; + } + + async start(): Promise { + this.msg = await this.channel.send(this.text); + + const period = 500; + + const icons = ['⚪', '🔵', '⚫']; + + const reactions = []; + + let i = 0; + let isRemoving = false; + (async () => { + while (!this.isStopped) { + if (reactions.length === icons.length) { + isRemoving = true; + } + + // eslint-disable-next-line no-await-in-loop + await delay(period); + + if (isRemoving) { + const reactionToRemove = reactions.shift(); + + if (reactionToRemove) { + // eslint-disable-next-line no-await-in-loop + await reactionToRemove.remove(); + } else { + isRemoving = false; + } + } else { + if (!this.isStopped) { + // eslint-disable-next-line no-await-in-loop + reactions.push(await this.msg.react(icons[i % icons.length])); + } + + i++; + } + } + })(); + } + + async stop(str?: string): Promise { + this.isStopped = true; + + if (str) { + await Promise.all([this.msg.reactions.removeAll(), this.msg.edit(str)]); + } else { + await this.msg.reactions.removeAll(); + } + + return this.msg; + } +} diff --git a/yarn.lock b/yarn.lock index 697c878..ce63305 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,11 +38,33 @@ node-addon-api "^2.0.0" node-pre-gyp "^0.14.0" +"@sindresorhus/is@^2.0.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.0.tgz#6ad4ca610f696098e92954ab431ff83bea0ce13f" + integrity sha512-lXKXfypKo644k4Da4yXkPCrwcvn6SlUW2X2zFbuflKHNjf0w9htru01bo26uMhleMXsDmnZ12eJLdrAZa9MANg== + +"@szmarczak/http-timer@^4.0.0": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152" + integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ== + dependencies: + defer-to-connect "^2.0.0" + "@types/bluebird@^3.5.30": version "3.5.30" resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.30.tgz#ee034a0eeea8b84ed868b1aa60d690b08a6cfbc5" integrity sha512-8LhzvcjIoqoi1TghEkRMkbbmM+jhHnBokPGkJWjclMK+Ks0MxEBow3/p2/iFTZ+OIbJHQDSfpgdZEb+af3gfVw== +"@types/cacheable-request@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976" + integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "*" + "@types/node" "*" + "@types/responselike" "*" + "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" @@ -53,21 +75,57 @@ resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== +"@types/http-cache-semantics@*": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a" + integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== + "@types/json-schema@^7.0.3": version "7.0.4" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA== -"@types/node@*", "@types/node@^13.9.0": +"@types/keyv@*": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7" + integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw== + dependencies: + "@types/node" "*" + +"@types/node@*", "@types/node@^13.5.3": version "13.9.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.0.tgz#5b6ee7a77faacddd7de719017d0bc12f52f81589" integrity sha512-0ARSQootUG1RljH2HncpsY2TJBfGQIKOOi7kxzUY6z54ePu/ZD+wJA8zI2Q6v8rol2qpG/rvqsReco8zNMPvhQ== +"@types/node@^13.9.1": + version "13.9.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.1.tgz#96f606f8cd67fb018847d9b61e93997dabdefc72" + integrity sha512-E6M6N0blf/jiZx8Q3nb0vNaswQeEyn0XlupO+xN6DtJ6r6IT4nXrTry7zhIfYvFCl3/8Cu6WIysmUBKiqV0bqQ== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/responselike@*": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" + integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== + dependencies: + "@types/node" "*" + +"@types/spotify-api@*": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@types/spotify-api/-/spotify-api-0.0.2.tgz#83bccd04fbc00de01d515418d77d658f277ef6f5" + integrity sha512-6WlUsg2xaSyxHZShTy+KNe4Hm8foDtaEVLe6+ID5DGOoByhcSKioCV2kPN6E8swgf2IHn+1o9knlbryYmoL0fw== + +"@types/spotify-web-api-node@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/spotify-web-api-node/-/spotify-web-api-node-4.0.1.tgz#0a2b85a1d40ae312de37deeb3c7e85bf7f4e6812" + integrity sha512-uxoz7DcSj/v2URDo54FjzctIfdQdEFvmUreHrR26G/Vf0Qm5S0lbt7LdKJeW2EoWUzzfjetYV3TcfMxRm1bWIw== + dependencies: + "@types/spotify-api" "*" + "@types/validator@^12.0.1": version "12.0.1" resolved "https://registry.yarnpkg.com/@types/validator/-/validator-12.0.1.tgz#73dbc7f5f730ff7131754bca682824eb3c260b79" @@ -80,40 +138,40 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@^2.22.0": - version "2.22.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.22.0.tgz#218ce6d4aa0244c6a40baba39ca1e021b26bb017" - integrity sha512-BvxRLaTDVQ3N+Qq8BivLiE9akQLAOUfxNHIEhedOcg8B2+jY8Rc4/D+iVprvuMX1AdezFYautuGDwr9QxqSxBQ== +"@typescript-eslint/eslint-plugin@^2.23.0": + version "2.23.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.23.0.tgz#aa7133bfb7b685379d9eafe4ae9e08b9037e129d" + integrity sha512-8iA4FvRsz8qTjR0L/nK9RcRUN3QtIHQiOm69FzV7WS3SE+7P7DyGGwh3k4UNR2JBbk+Ej2Io+jLAaqKibNhmtw== dependencies: - "@typescript-eslint/experimental-utils" "2.22.0" + "@typescript-eslint/experimental-utils" "2.23.0" eslint-utils "^1.4.3" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@2.22.0": - version "2.22.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.22.0.tgz#4d00c91fbaaa68e56e7869be284999a265707f85" - integrity sha512-sJt1GYBe6yC0dWOQzXlp+tiuGglNhJC9eXZeC8GBVH98Zv9jtatccuhz0OF5kC/DwChqsNfghHx7OlIDQjNYAQ== +"@typescript-eslint/experimental-utils@2.23.0": + version "2.23.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.23.0.tgz#5d2261c8038ec1698ca4435a8da479c661dc9242" + integrity sha512-OswxY59RcXH3NNPmq+4Kis2CYZPurRU6mG5xPcn24CjFyfdVli5mySwZz/g/xDbJXgDsYqNGq7enV0IziWGXVQ== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.22.0" + "@typescript-eslint/typescript-estree" "2.23.0" eslint-scope "^5.0.0" -"@typescript-eslint/parser@^2.22.0": - version "2.22.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.22.0.tgz#8eeb6cb6de873f655e64153397d4790898e149d0" - integrity sha512-FaZKC1X+nvD7qMPqKFUYHz3H0TAioSVFGvG29f796Nc5tBluoqfHgLbSFKsh7mKjRoeTm8J9WX2Wo9EyZWjG7w== +"@typescript-eslint/parser@^2.23.0": + version "2.23.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.23.0.tgz#f3d4e2928ff647fe77fc2fcef1a3534fee6a3212" + integrity sha512-k61pn/Nepk43qa1oLMiyqApC6x5eP5ddPz6VUYXCAuXxbmRLqkPYzkFRKl42ltxzB2luvejlVncrEpflgQoSUg== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.22.0" - "@typescript-eslint/typescript-estree" "2.22.0" + "@typescript-eslint/experimental-utils" "2.23.0" + "@typescript-eslint/typescript-estree" "2.23.0" eslint-visitor-keys "^1.1.0" -"@typescript-eslint/typescript-estree@2.22.0": - version "2.22.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.22.0.tgz#a16ed45876abf743e1f5857e2f4a1c3199fd219e" - integrity sha512-2HFZW2FQc4MhIBB8WhDm9lVFaBDy6h9jGrJ4V2Uzxe/ON29HCHBTj3GkgcsgMWfsl2U5as+pTOr30Nibaw7qRQ== +"@typescript-eslint/typescript-estree@2.23.0": + version "2.23.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.23.0.tgz#d355960fab96bd550855488dcc34b9a4acac8d36" + integrity sha512-pmf7IlmvXdlEXvE/JWNNJpEvwBV59wtJqA8MLAxMKLXNKVRC3HZBXR/SlZLPWTCcwOSg9IM7GeRSV3SIerGVqw== dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0" @@ -274,6 +332,13 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== +axios@^0.19.0: + version "0.19.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" + integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== + dependencies: + follow-redirects "1.5.10" + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -329,6 +394,26 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +cacheable-lookup@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-2.0.0.tgz#33b1e56f17507f5cf9bb46075112d65473fb7713" + integrity sha512-s2piO6LvA7xnL1AR03wuEdSx3BZT3tIJpZ56/lcJwzO/6DTJZlTs7X3lrvPxk6d1PlDe6PrVe2TjlUIZNFglAQ== + dependencies: + keyv "^4.0.0" + +cacheable-request@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58" + integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^2.0.0" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -418,6 +503,13 @@ cli-width@^2.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + dependencies: + mimic-response "^1.0.0" + cls-bluebird@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cls-bluebird/-/cls-bluebird-2.1.0.tgz#37ef1e080a8ffb55c2f4164f536f1919e7968aee" @@ -467,6 +559,11 @@ compare-versions@^3.5.1: resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== +component-emitter@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -494,6 +591,11 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= +cookiejar@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" + integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA== + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -549,6 +651,13 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +debug@=3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + debug@^2.2.0: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -556,7 +665,7 @@ debug@^2.2.0: dependencies: ms "2.0.0" -debug@^3.2.6: +debug@^3.1.0, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -570,6 +679,13 @@ debug@^4.0.1, debug@^4.1.1: dependencies: ms "^2.1.1" +decompress-response@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-5.0.0.tgz#7849396e80e3d1eba8cb2f75ef4930f76461cb0f" + integrity sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw== + dependencies: + mimic-response "^2.0.0" + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -580,6 +696,16 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= +defer-to-connect@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1" + integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg== + +delay@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/delay/-/delay-4.3.0.tgz#efeebfb8f545579cb396b3a722443ec96d14c50e" + integrity sha512-Lwaf3zVFDMBop1yDuFZ19F9WyGcZcGacsbdlZtWjQmM50tOcMntm1njF/Nb/Vjij3KaSvCF+sEYGKrrjObu2NA== + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -600,10 +726,10 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -discord.js@^12.0.1: - version "12.0.1" - resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-12.0.1.tgz#58574c0c9acc598095f943d6b14da4725d37b8b9" - integrity sha512-lUlrkAWSb5YTB1WpSZHjeUXxGlHK8VDjrlHLEP4lJj+etFAellURpmRYl29OPJ/7arQWB879pP4rvhhzpdOF7w== +discord.js@^12.0.2: + version "12.0.2" + resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-12.0.2.tgz#c4d68f1363d7fc05ed71a42dba6b930966ed8602" + integrity sha512-iZiEA4Y61gqq/EjFfLXnkRK9pLapnax/vTVDUhs/mAhyqozAy0GOlk/MZI9rSa1iIoKTWRq6P9CRKhLNT2wUnA== dependencies: "@discordjs/collection" "^0.1.5" abort-controller "^3.0.0" @@ -661,6 +787,13 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -804,7 +937,7 @@ execa@^0.7.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -extend@~3.0.2: +extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -893,11 +1026,27 @@ flatted@^2.0.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08" integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg== +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== + dependencies: + debug "=3.1.0" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +form-data@^2.3.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" + integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + form-data@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" @@ -916,6 +1065,11 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" +formidable@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9" + integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q== + fs-minipass@^1.2.5: version "1.2.7" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" @@ -957,6 +1111,13 @@ get-stream@^3.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= +get-stream@^5.0.0, get-stream@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" + integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== + dependencies: + pump "^3.0.0" + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -1009,6 +1170,27 @@ globals@^12.1.0: dependencies: type-fest "^0.8.1" +got@^10.6.0: + version "10.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-10.6.0.tgz#ac3876261a4d8e5fc4f81186f79955ce7b0501dc" + integrity sha512-3LIdJNTdCFbbJc+h/EH0V5lpNpbJ6Bfwykk21lcQvQsEcrzdi/ltCyQehFHLzJ/ka0UMH4Slg0hkYvAZN9qUDg== + dependencies: + "@sindresorhus/is" "^2.0.0" + "@szmarczak/http-timer" "^4.0.0" + "@types/cacheable-request" "^6.0.1" + cacheable-lookup "^2.0.0" + cacheable-request "^7.0.1" + decompress-response "^5.0.0" + duplexer3 "^0.1.4" + get-stream "^5.0.0" + lowercase-keys "^2.0.0" + mimic-response "^2.1.0" + p-cancelable "^2.0.0" + p-event "^4.0.0" + responselike "^2.0.0" + to-readable-stream "^2.0.0" + type-fest "^0.10.0" + got@^6.7.1: version "6.7.1" resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" @@ -1072,6 +1254,11 @@ html-entities@^1.1.3: resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8= +http-cache-semantics@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -1181,6 +1368,11 @@ inquirer@^7.0.0: strip-ansi "^6.0.0" through "^2.3.6" +inversify@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/inversify/-/inversify-5.0.1.tgz#500d709b1434896ce5a0d58915c4a4210e34fb6e" + integrity sha512-Ieh06s48WnEYGcqHepdsJUIJUXpwH5o5vodAX+DK2JA/gjy4EbEcQZxw+uFfzysmKjiLXGYwNG3qDZsKVMcINQ== + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -1304,6 +1496,11 @@ 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.2.0" + resolved "https://registry.yarnpkg.com/iso8601-duration/-/iso8601-duration-1.2.0.tgz#5fa6fc180a8fe95ad6a6721c9bdd9069cb59e80e" + integrity sha512-ErTBd++b17E8nmWII1K1uZtBgD1E8RjyvwmxlCjPHNqHMD7gmcMHOw0E8Ro/6+QT4PhHRSnnMo7bxa1vFPkwhg== + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -1327,6 +1524,11 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -1362,6 +1564,13 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +keyv@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.0.tgz#2d1dab694926b2d427e4c74804a10850be44c12f" + integrity sha512-U7ioE8AimvRVLfw4LffyOIRhL2xVgmE8T22L6i0BucSnBUyv4w+I7VN/zVZwRKHOI6ZRUcdMdWHQ8KSUvGpEog== + dependencies: + json-buffer "3.0.1" + latest-version@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" @@ -1404,6 +1613,11 @@ lowercase-keys@^1.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lru-cache@^4.0.1: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" @@ -1439,6 +1653,11 @@ make-error@^1.1.1: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +methods@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + mime-db@1.43.0: version "1.43.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" @@ -1451,11 +1670,26 @@ mime-types@^2.1.12, mime-types@~2.1.19: dependencies: mime-db "1.43.0" +mime@^1.4.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-response@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +mimic-response@^2.0.0, mimic-response@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" + integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== + miniget@^1.6.0, miniget@^1.6.1: version "1.7.0" resolved "https://registry.yarnpkg.com/miniget/-/miniget-1.7.0.tgz#a29eb79ebff479e9efafd271616981c603987875" @@ -1636,6 +1870,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +normalize-url@^4.1.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" + integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== + npm-bundled@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" @@ -1689,7 +1928,7 @@ object-assign@^4.1.0: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -once@^1.3.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= @@ -1738,12 +1977,24 @@ osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +p-cancelable@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" + integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== + +p-event@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.1.0.tgz#e92bb866d7e8e5b732293b1c8269d38e9982bf8e" + integrity sha512-4vAd06GCsgflX4wHN1JqrMzBh/8QZ4j+rzp0cd2scXRwuBEv+QR3wrVA5aLhWDLw4y2WgDKvzWF3CCLmVM1UgA== + dependencies: + p-timeout "^2.0.1" + p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= -p-limit@^2.2.0: +p-limit@^2.2.0, p-limit@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== @@ -1757,6 +2008,13 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" +p-timeout@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" + integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA== + dependencies: + p-finally "^1.0.0" + p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -1853,7 +2111,7 @@ prepend-http@^1.0.1: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= -prism-media@^1.2.0: +prism-media@^1.0.1, prism-media@^1.2.0, prism-media@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prism-media/-/prism-media-1.2.1.tgz#168f323712bcaacb1d70ae613bf9d9dc44cf43d4" integrity sha512-R3EbKwJiYlTvGwcG1DpUt+06DsxOGS5W4AMEHT7oVOjG93MjpdhGX1whHyjnqknylLMupKAsKMEXcTNRbPe6Vw== @@ -1883,11 +2141,24 @@ pstree.remy@^1.1.7: resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.7.tgz#c76963a28047ed61542dc361aa26ee55a7fa15f3" integrity sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A== +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +qs@^6.5.1: + version "6.9.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.1.tgz#20082c65cb78223635ab1a9eaca8875a29bf8ec9" + integrity sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA== + qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -1903,7 +2174,7 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -readable-stream@^2.0.6: +readable-stream@^2.0.6, readable-stream@^2.3.5: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -1989,6 +2260,13 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +responselike@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" + integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== + dependencies: + lowercase-keys "^2.0.0" + restore-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" @@ -2171,6 +2449,18 @@ source-map@^0.6.0: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +spotify-uri@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/spotify-uri/-/spotify-uri-2.0.0.tgz#01c4cb1696d3eb803bf7054720efda9e66b8d553" + integrity sha512-GmEDCx74boSaJFgyUGEKVnVTinCNF5f8RMIUsM8MKJSSaeQ/qonipY42NFct4pFbUT3MS5A19z/Dduy1dHdvaQ== + +spotify-web-api-node@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/spotify-web-api-node/-/spotify-web-api-node-4.0.0.tgz#55f060975220cdac18efc0e781f84130b12004c0" + integrity sha512-FQAX4qiP9xfjmJpkSfF5PEVr7RVorUZiLvcdVTlhVFLYAmQ8VSsZlyb0yTK0GExKhAcgJy9GfWxqjSB2r9SrjA== + dependencies: + superagent "^3.7.0" + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -2285,6 +2575,22 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= +superagent@^3.7.0: + version "3.8.3" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128" + integrity sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA== + dependencies: + component-emitter "^1.2.0" + cookiejar "^2.1.0" + debug "^3.1.0" + extend "^3.0.0" + form-data "^2.3.1" + formidable "^1.2.0" + methods "^1.1.1" + mime "^1.4.1" + qs "^6.5.1" + readable-stream "^2.3.5" + supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -2351,6 +2657,11 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" +to-readable-stream@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-2.1.0.tgz#82880316121bea662cdc226adb30addb50cb06e8" + integrity sha512-o3Qa6DGg1CEXshSdvWNX2sN4QHqg03SPq7U6jPXRahlQdl5dK8oXjkU/2/sGrnOZKeGV1zLSO8qPwyKklPPE7w== + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -2425,6 +2736,11 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" +type-fest@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.10.0.tgz#7f06b2b9fbfc581068d1341ffabd0349ceafc642" + integrity sha512-EUV9jo4sffrwlg8s0zDhP0T2WD3pru5Xi0+HTE3zTUmBaZNhfkite9PdSJwdXLwPVW0jnAHT56pZHIOYckPEiw== + type-fest@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" @@ -2609,6 +2925,33 @@ yn@3.1.1: resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== +youtube.ts@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/youtube.ts/-/youtube.ts-0.1.0.tgz#9925c59dadf1b9dcf0916aec247667d6cefff6de" + integrity sha512-1JB3w8oCv2nrTv332cs2VZ5X2tWLJrVlq2m7REMGCQmHLxxr7y0yCFbM+69MzmItCgQ6ThijMYb1w2vDmzq7Uw== + dependencies: + axios "^0.19.0" + ytdl-core-discord "^1.1.0" + +ytdl-core-discord@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ytdl-core-discord/-/ytdl-core-discord-1.1.0.tgz#1276ef3895b773a3eafa4fe323495b41b715b9e1" + integrity sha512-uiaZWa9UG+he1F9p7Si9H6Tieyxd9dEhLi4958mHuebudQSEEPaaJHUEbFikcpB++5ogzynVOcvdeC+LFJGgEw== + dependencies: + "@types/node" "^13.5.3" + prism-media "^1.0.1" + ytdl-core "^1.0.3" + +ytdl-core@^1.0.3: + version "1.0.9" + resolved "https://registry.yarnpkg.com/ytdl-core/-/ytdl-core-1.0.9.tgz#938d5bf5f2baf901b04ffe41d4444fba72ba283a" + integrity sha512-HhFeLfjXU34h0FNHmSkSpKygdaYijSt8VNsC770VYBRFb+dyUKcm11cIKxu2MUSwT9znISZ0k1wFdaV/N5VW+Q== + dependencies: + html-entities "^1.1.3" + m3u8stream "^0.6.3" + miniget "^1.6.0" + sax "^1.1.3" + ytdl-core@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ytdl-core/-/ytdl-core-2.0.0.tgz#09bafc2beeab1eb9c69ceb9ca8f406be12396613" @@ -2618,3 +2961,10 @@ ytdl-core@^2.0.0: m3u8stream "^0.6.3" miniget "^1.6.0" sax "^1.1.3" + +ytsr@^0.1.11: + version "0.1.11" + resolved "https://registry.yarnpkg.com/ytsr/-/ytsr-0.1.11.tgz#223041db1f610b9c4453e4be63764a775e0c74ba" + integrity sha512-f7RILud27ufqsw3+Zi0J5itQ7qOt7BzN2EhqQARFhZq9HdxrlSfwvymiO/wvzFALCR2bgyxfz/cJhWyjUlokCA== + dependencies: + html-entities "^1.1.3"