diff --git a/src/bot.ts b/src/bot.ts index d6d6903..b54e665 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -2,9 +2,10 @@ import {Client, Message, Collection} from 'discord.js'; import {inject, injectable} from 'inversify'; import {TYPES} from './types'; import {Settings, Shortcut} from './models'; -import handleGuildCreate from './events/guild-create'; import container from './inversify.config'; import Command from './commands'; +import handleGuildCreate from './events/guild-create'; +import handleVoiceStateUpdate from './events/voice-state-update'; @injectable() export default class { @@ -87,12 +88,11 @@ export default class { console.log(`Ready! Invite the bot with https://discordapp.com/oauth2/authorize?client_id=${this.clientId}&scope=bot`); }); - this.client.on('error', error => { - console.error(error); - }); + this.client.on('error', console.error); // Register event handlers this.client.on('guildCreate', handleGuildCreate); + this.client.on('voiceStateUpdate', handleVoiceStateUpdate); return this.client.login(this.token); } diff --git a/src/commands/play.ts b/src/commands/play.ts index b354e5a..4ba04bd 100644 --- a/src/commands/play.ts +++ b/src/commands/play.ts @@ -36,6 +36,13 @@ export default class implements Command { } public async execute(msg: Message, args: string []): Promise { + const [targetVoiceChannel, nInChannel] = getMostPopularVoiceChannel(msg.guild!); + + if (nInChannel === 0) { + await msg.channel.send('error: all voice channels are empty'); + return; + } + const queue = this.queueManager.get(msg.guild!.id); if (args.length === 0) { @@ -50,9 +57,7 @@ export default class implements Command { return; } - const channel = getMostPopularVoiceChannel(msg.guild!); - - await this.playerManager.get(msg.guild!.id).connect(channel); + await this.playerManager.get(msg.guild!.id).connect(targetVoiceChannel); await this.playerManager.get(msg.guild!.id).play(); await msg.channel.send('play resuming'); @@ -252,9 +257,7 @@ export default class implements Command { await res.stop('song(s) queued'); if (this.playerManager.get(msg.guild!.id).status === STATUS.DISCONNECTED) { - const channel = getMostPopularVoiceChannel(msg.guild!); - - await this.playerManager.get(msg.guild!.id).connect(channel); + await this.playerManager.get(msg.guild!.id).connect(targetVoiceChannel); await this.playerManager.get(msg.guild!.id).play(); } diff --git a/src/events/voice-state-update.ts b/src/events/voice-state-update.ts new file mode 100644 index 0000000..c16177d --- /dev/null +++ b/src/events/voice-state-update.ts @@ -0,0 +1,17 @@ +import {VoiceState} from 'discord.js'; +import container from '../inversify.config'; +import {TYPES} from '../types'; +import PlayerManager from '../managers/player'; +import {getSizeWithoutBots} from '../utils/channels'; + +export default (oldState: VoiceState, _: VoiceState): void => { + const playerManager = container.get(TYPES.Managers.Player); + + const player = playerManager.get(oldState.guild.id); + + if (player.voiceConnection) { + if (getSizeWithoutBots(player.voiceConnection.channel) === 0) { + player.disconnect(); + } + } +}; diff --git a/src/services/player.ts b/src/services/player.ts index be2cdc0..2465a8a 100644 --- a/src/services/player.ts +++ b/src/services/player.ts @@ -16,9 +16,9 @@ export enum STATUS { export default class { public status = STATUS.DISCONNECTED; + public voiceConnection: VoiceConnection | null = null; private readonly queue: Queue; private readonly cacheDir: string; - private voiceConnection: VoiceConnection | null = null; private dispatcher: StreamDispatcher | null = null; private playPositionInterval: NodeJS.Timeout | undefined; diff --git a/src/utils/channels.ts b/src/utils/channels.ts index d138c45..bd56dd4 100644 --- a/src/utils/channels.ts +++ b/src/utils/channels.ts @@ -1,6 +1,14 @@ import {Guild, VoiceChannel} from 'discord.js'; -export const getMostPopularVoiceChannel = (guild: Guild, min = 0): VoiceChannel => { +export const getSizeWithoutBots = (channel: VoiceChannel): number => channel.members.array().reduce((s, member) => { + if (!member.user.bot) { + s++; + } + + return s; +}, 0); + +export const getMostPopularVoiceChannel = (guild: Guild): [VoiceChannel, number] => { interface PopularResult { n: number; channel: VoiceChannel | null; @@ -9,18 +17,16 @@ export const getMostPopularVoiceChannel = (guild: Guild, min = 0): VoiceChannel const voiceChannels: PopularResult[] = []; for (const [_, channel] of guild.channels.cache) { - if (channel.type === 'voice' && channel.members.size >= min) { + if (channel.type === 'voice') { + const size = getSizeWithoutBots(channel as VoiceChannel); + voiceChannels.push({ channel: channel as VoiceChannel, - n: channel.members.size + n: size }); } } - if (voiceChannels.length === 0) { - throw new Error('No voice channels meet minimum size'); - } - // Find most popular channel const popularChannel = voiceChannels.reduce((popular: PopularResult, elem: PopularResult) => { if (elem.n > popular.n) { @@ -31,7 +37,7 @@ export const getMostPopularVoiceChannel = (guild: Guild, min = 0): VoiceChannel }, {n: -1, channel: null}); if (popularChannel.channel) { - return popularChannel.channel; + return [popularChannel.channel, popularChannel.n]; } throw new Error();