aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bot.ts11
-rw-r--r--src/inversify.config.ts2
-rw-r--r--src/services/natural-language-commands.ts76
-rw-r--r--src/services/player.ts102
-rw-r--r--src/types.ts3
5 files changed, 138 insertions, 56 deletions
diff --git a/src/bot.ts b/src/bot.ts
index 0ca77e0..485becd 100644
--- a/src/bot.ts
+++ b/src/bot.ts
@@ -5,18 +5,21 @@ import {Settings, Shortcut} from './models';
import container from './inversify.config';
import Command from './commands';
import debug from './utils/debug';
+import NaturalLanguage from './services/natural-language-commands';
import handleGuildCreate from './events/guild-create';
import handleVoiceStateUpdate from './events/voice-state-update';
@injectable()
export default class {
private readonly client: Client;
+ private readonly naturalLanguage: NaturalLanguage;
private readonly token: string;
private readonly clientId: 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) {
+ constructor(@inject(TYPES.Client) client: Client, @inject(TYPES.Services.NaturalLanguage) naturalLanguage: NaturalLanguage, @inject(TYPES.Config.DISCORD_TOKEN) token: string, @inject(TYPES.Config.DISCORD_CLIENT_ID) clientId: string) {
this.client = client;
+ this.naturalLanguage = naturalLanguage;
this.token = token;
this.clientId = clientId;
this.commands = new Collection();
@@ -41,10 +44,8 @@ export default class {
return this.client.emit('guildCreate', msg.guild);
}
- if (msg.content.startsWith('say') && msg.content.endsWith('muse')) {
- const res = msg.content.slice(3, msg.content.indexOf('muse')).trim();
-
- await msg.channel.send(res);
+ if (await this.naturalLanguage.execute(msg)) {
+ // Natural language command handled message
return;
}
diff --git a/src/inversify.config.ts b/src/inversify.config.ts
index 463e08a..590d8f5 100644
--- a/src/inversify.config.ts
+++ b/src/inversify.config.ts
@@ -21,6 +21,7 @@ import QueueManager from './managers/queue';
// Helpers
import GetSongs from './services/get-songs';
+import NaturalLanguage from './services/natural-language-commands';
// Comands
import Command from './commands';
@@ -49,6 +50,7 @@ container.bind<QueueManager>(TYPES.Managers.Queue).to(QueueManager).inSingletonS
// Helpers
container.bind<GetSongs>(TYPES.Services.GetSongs).to(GetSongs).inSingletonScope();
+container.bind<NaturalLanguage>(TYPES.Services.NaturalLanguage).to(NaturalLanguage).inSingletonScope();
// Commands
container.bind<Command>(TYPES.Command).to(Clear).inSingletonScope();
diff --git a/src/services/natural-language-commands.ts b/src/services/natural-language-commands.ts
new file mode 100644
index 0000000..5d5b7d5
--- /dev/null
+++ b/src/services/natural-language-commands.ts
@@ -0,0 +1,76 @@
+import {inject, injectable} from 'inversify';
+import {Message} from 'discord.js';
+import {TYPES} from '../types';
+import PlayerManager from '../managers/player';
+import QueueManager from '../managers/queue';
+import {getMostPopularVoiceChannel} from '../utils/channels';
+
+@injectable()
+export default class {
+ private readonly playerManager: PlayerManager;
+ private readonly queueManager: QueueManager;
+
+ constructor(@inject(TYPES.Managers.Player) playerManager: PlayerManager, @inject(TYPES.Managers.Queue) queueManager: QueueManager) {
+ this.playerManager = playerManager;
+ this.queueManager = queueManager;
+ }
+
+ async execute(msg: Message): Promise<boolean> {
+ if (msg.content.startsWith('say') && msg.content.endsWith('muse')) {
+ const res = msg.content.slice(3, msg.content.indexOf('muse')).trim();
+
+ await msg.channel.send(res);
+ return true;
+ }
+
+ if (msg.content.includes('packers')) {
+ const queue = this.queueManager.get(msg.guild!.id);
+ const player = this.playerManager.get(msg.guild!.id);
+
+ const [channel, n] = getMostPopularVoiceChannel(msg.guild!);
+
+ await msg.channel.send('GO PACKERS GO!!!');
+
+ if (!player.voiceConnection && n === 0) {
+ return false;
+ }
+
+ if (!player.voiceConnection) {
+ await player.connect(channel);
+ }
+
+ const isPlaying = queue.getCurrent();
+ let oldPosition = 0;
+
+ queue.add({title: 'GO PACKERS!', artist: 'Unknown', url: 'https://www.youtube.com/watch?v=qkdtID7mY3E', length: 204, playlist: null, isLive: false});
+
+ if (isPlaying) {
+ oldPosition = player.getPosition();
+ queue.forward();
+ }
+
+ await player.seek(8);
+
+ return new Promise((resolve, reject) => {
+ try {
+ setTimeout(async () => {
+ if (isPlaying) {
+ queue.back();
+
+ await player.seek(oldPosition);
+ } else {
+ queue.forward();
+ player.disconnect();
+ }
+
+ resolve(true);
+ }, 10000);
+ } catch (error) {
+ reject(error);
+ }
+ });
+ }
+
+ return false;
+ }
+}
diff --git a/src/services/player.ts b/src/services/player.ts
index 690d1f7..38a92aa 100644
--- a/src/services/player.ts
+++ b/src/services/player.ts
@@ -68,12 +68,8 @@ export default class {
throw new Error('Seek position is outside the range of the song.');
}
- if (await this.isCached(currentSong.url)) {
- this.dispatcher = this.voiceConnection.play(this.getCachedPath(currentSong.url), {seek: positionSeconds});
- } else {
- const stream = await this.getStream(currentSong.url, {seek: positionSeconds});
- this.dispatcher = this.voiceConnection.play(stream, {type: 'webm/opus'});
- }
+ const stream = await this.getStream(currentSong.url, {seek: positionSeconds});
+ this.dispatcher = this.voiceConnection.play(stream, {type: 'webm/opus'});
this.attachListeners();
this.startTrackingPosition(positionSeconds);
@@ -107,12 +103,8 @@ export default class {
return;
}
- if (await this.isCached(currentSong.url)) {
- this.dispatcher = this.voiceConnection.play(this.getCachedPath(currentSong.url));
- } else {
- const stream = await this.getStream(currentSong.url);
- this.dispatcher = this.voiceConnection.play(stream, {type: 'webm/opus'});
- }
+ const stream = await this.getStream(currentSong.url);
+ this.dispatcher = this.voiceConnection.play(stream, {type: 'webm/opus'});
this.attachListeners();
@@ -154,67 +146,77 @@ export default class {
}
}
- private async getStream(url: string, options: {seek?: number} = {}): Promise<Readable|string> {
+ private async getStream(url: string, options: {seek?: number} = {}): Promise<Readable> {
const cachedPath = this.getCachedPath(url);
+ let ffmpegInput = '';
+ const ffmpegInputOptions = [];
+ let shouldCacheVideo = false;
+
if (await this.isCached(url)) {
- return cachedPath;
- }
+ ffmpegInput = cachedPath;
+ } else {
+ // Not yet cached, must download
+ const info = await ytdl.getInfo(url);
+
+ const {formats} = info;
- // Not yet cached, must download
- const info = await ytdl.getInfo(url);
+ const filter = (format: ytdl.videoFormat): boolean => format.codecs === 'opus' && format.container === 'webm' && format.audioSampleRate !== undefined && parseInt(format.audioSampleRate, 10) === 48000;
- const {formats} = info;
+ let format = formats.find(filter);
- const filter = (format: ytdl.videoFormat): boolean => format.codecs === 'opus' && format.container === 'webm' && format.audioSampleRate !== undefined && parseInt(format.audioSampleRate, 10) === 48000;
+ const nextBestFormat = (formats: ytdl.videoFormat[]): ytdl.videoFormat | undefined => {
+ if (formats[0].live) {
+ formats = formats.sort((a, b) => (b as any).audioBitrate - (a as any).audioBitrate); // Bad typings
- let format = formats.find(filter);
+ return formats.find(format => [128, 127, 120, 96, 95, 94, 93].includes(parseInt(format.itag as unknown as string, 10))); // Bad typings
+ }
+
+ formats = formats
+ .filter(format => format.averageBitrate)
+ .sort((a, b) => b.averageBitrate - a.averageBitrate);
+ return formats.find(format => !format.bitrate) ?? formats[0];
+ };
- const nextBestFormat = (formats: ytdl.videoFormat[]): ytdl.videoFormat | undefined => {
- if (formats[0].live) {
- formats = formats.sort((a, b) => (b as any).audioBitrate - (a as any).audioBitrate); // Bad typings
+ if (!format) {
+ format = nextBestFormat(info.formats);
- return formats.find(format => [128, 127, 120, 96, 95, 94, 93].includes(parseInt(format.itag as unknown as string, 10))); // Bad typings
+ if (!format) {
+ // If still no format is found, throw
+ throw new Error('Can\'t find suitable format.');
+ }
}
- formats = formats
- .filter(format => format.averageBitrate)
- .sort((a, b) => b.averageBitrate - a.averageBitrate);
- return formats.find(format => !format.bitrate) ?? formats[0];
- };
+ ffmpegInput = format.url;
- if (!format) {
- format = nextBestFormat(info.formats);
+ // Don't cache livestreams or long videos
+ const MAX_CACHE_LENGTH_SECONDS = 30 * 60; // 30 minutes
+ shouldCacheVideo = !info.player_response.videoDetails.isLiveContent && parseInt(info.length_seconds, 10) < MAX_CACHE_LENGTH_SECONDS;
- if (!format) {
- // If still no format is found, throw
- throw new Error('Can\'t find suitable format.');
- }
+ ffmpegInputOptions.push(...[
+ '-reconnect',
+ '1',
+ '-reconnect_streamed',
+ '1',
+ '-reconnect_delay_max',
+ '5'
+ ]);
}
- const inputOptions = [
- '-reconnect',
- '1',
- '-reconnect_streamed',
- '1',
- '-reconnect_delay_max',
- '5'
- ];
-
+ // Add seek parameter if necessary
if (options.seek) {
- inputOptions.push('-ss', options.seek.toString());
+ ffmpegInputOptions.push('-ss', options.seek.toString());
}
- const youtubeStream = ffmpeg(format.url).inputOptions(inputOptions).noVideo().audioCodec('libopus').outputFormat('webm').pipe() as PassThrough;
+ // Create stream and pipe to capacitor
+ const youtubeStream = ffmpeg(ffmpegInput).inputOptions(ffmpegInputOptions).noVideo().audioCodec('libopus').outputFormat('webm').pipe() as PassThrough;
const capacitor = new WriteStream();
youtubeStream.pipe(capacitor);
- // Don't cache livestreams or long videos
- const MAX_CACHE_LENGTH_SECONDS = 30 * 60; // 30 minutes
-
- if (!info.player_response.videoDetails.isLiveContent && parseInt(info.length_seconds, 10) < MAX_CACHE_LENGTH_SECONDS) {
+ // Cache video if necessary
+ if (shouldCacheVideo) {
const cacheTempPath = this.getCachedPathTemp(url);
const cacheStream = createWriteStream(cacheTempPath);
diff --git a/src/types.ts b/src/types.ts
index 0384bc9..ee18fb8 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -18,6 +18,7 @@ export const TYPES = {
Queue: Symbol('QueueManager')
},
Services: {
- GetSongs: Symbol('GetSongs')
+ GetSongs: Symbol('GetSongs'),
+ NaturalLanguage: Symbol('NaturalLanguage')
}
};