aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package.json21
-rw-r--r--src/bot.ts86
-rw-r--r--src/commands/config.ts19
-rw-r--r--src/commands/index.ts7
-rw-r--r--src/commands/play.ts212
-rw-r--r--src/commands/queue.ts22
-rw-r--r--src/index.ts75
-rw-r--r--src/interfaces.ts7
-rw-r--r--src/inversify.config.ts54
-rw-r--r--src/packages.d.ts1
-rw-r--r--src/services/player.ts88
-rw-r--r--src/services/queue.ts89
-rw-r--r--src/types.ts20
-rw-r--r--src/utils/config.ts3
-rw-r--r--src/utils/get-youtube-stream.ts29
-rw-r--r--src/utils/loading-message.ts67
-rw-r--r--yarn.lock412
17 files changed, 1081 insertions, 131 deletions
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<string, Command>;
+
+ 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<string> {
+ // Load in commands
+ container.getAll<Command>(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<void> {
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<void>;
+}
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;
- const channel = getMostPopularVoiceChannel(msg.guild!);
+ 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;
+ }
- const conn = await channel.join();
+ public async execute(msg: Message, args: string []): Promise<void> {
+ const newSongs: QueuedSong[] = [];
- const stream = await getYouTubeStream(url);
+ const res = new LoadingMessage(msg.channel as TextChannel, 'hold on a sec');
+ await res.start();
- conn.play(stream, {type: 'webm/opus'});
- }
-};
+ const addSingleSong = async (source: string): Promise<void> => {
+ 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)
+ }));
-export default play;
+ 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<QueuedSong|null> => {
+ 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!);
+
+ // TODO: don't connect if already connected.
+ await this.player.connect(msg.guild!.id, channel);
+
+ await this.player.play(msg.guild!.id);
+ }
+}
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<void> {
+ 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<Bot>(TYPES.Bot);
+const spotify = container.get<Spotify>(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<Bot>(TYPES.Bot).to(Bot).inSingletonScope();
+container.bind<Client>(TYPES.Client).toConstantValue(new Client());
+
+// Services
+container.bind<Player>(TYPES.Services.Player).to(Player).inSingletonScope();
+container.bind<Queue>(TYPES.Services.Queue).to(Queue).inSingletonScope();
+
+// Commands
+container.bind<Command>(TYPES.Command).to(Config).inSingletonScope();
+container.bind<Command>(TYPES.Command).to(Play).inSingletonScope();
+container.bind<Command>(TYPES.Command).to(QueueCommad).inSingletonScope();
+
+// Config values
+container.bind<string>(TYPES.Config.DISCORD_TOKEN).toConstantValue(DISCORD_TOKEN);
+container.bind<string>(TYPES.Config.DISCORD_CLIENT_ID).toConstantValue(DISCORD_CLIENT_ID);
+container.bind<string>(TYPES.Config.YOUTUBE_API_KEY).toConstantValue(YOUTUBE_API_KEY);
+container.bind<string>(TYPES.Config.DATA_DIR).toConstantValue(DATA_DIR);
+container.bind<string>(TYPES.Config.CACHE_DIR).toConstantValue(CACHE_DIR);
+
+// Static libraries
+container.bind<YouTube>(TYPES.Lib.YouTube).toConstantValue(new YouTube(YOUTUBE_API_KEY));
+container.bind<Spotify>(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<string, GuildPlayer>();
+ private readonly queue: Queue;
+
+ constructor(@inject(TYPES.Services.Queue) queue: Queue) {
+ this.queue = queue;
+ }
+
+ async connect(guildId: string, channel: VoiceChannel): Promise<void> {
+ 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<void> {
+ 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<string, QueuedSong[]>();
+ private readonly queuePositions = new Map<string, number>();
+
+ 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<Readable> => {
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<Readable> => {
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<void> {
+ 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<Message> {
+ 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/[email protected]":
- 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/[email protected]":
+ 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/[email protected]":
- 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/[email protected]":
+ 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==
+
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==
+
[email protected], 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==
+ 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=
+ 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=
+
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 @@ [email protected]:
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"