aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJohannes Vääräkangas <[email protected]>2022-02-12 04:05:02 +0200
committerGitHub <[email protected]>2022-02-11 20:05:02 -0600
commit4dbb55a72119a61033d2df6e8cf46f5eaa7bba15 (patch)
treec23bfee7a2c18a18ac8536a6b31e0e22eb1d9c6e /src
parent8e5b3cfa432abc4b3e2ef76a139da2586ccb1213 (diff)
downloadmuse-4dbb55a72119a61033d2df6e8cf46f5eaa7bba15.tar.xz
muse-4dbb55a72119a61033d2df6e8cf46f5eaa7bba15.zip
Configurable voice channel leave behavior (#514)
Co-authored-by: Max Isom <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/commands/config.ts53
-rw-r--r--src/events/voice-state-update.ts12
-rw-r--r--src/services/add-query-to-queue.ts5
-rw-r--r--src/services/player.ts32
4 files changed, 97 insertions, 5 deletions
diff --git a/src/commands/config.ts b/src/commands/config.ts
index 65aa36b..b0a5215 100644
--- a/src/commands/config.ts
+++ b/src/commands/config.ts
@@ -25,6 +25,21 @@ export default class implements Command {
.setDescription('allowed role')
.setRequired(true)))
.addSubcommand(subcommand => subcommand
+ .setName('set-wait-after-queue-empties')
+ .setDescription('set the time to wait before leaving the voice channel when queue empties')
+ .addIntegerOption(option => option
+ .setName('delay')
+ .setDescription('delay in seconds (set to 0 to never leave)')
+ .setRequired(true)
+ .setMinValue(0)))
+ .addSubcommand(subcommand => subcommand
+ .setName('set-leave-if-no-listeners')
+ .setDescription('set whether to leave when all other participants leave')
+ .addBooleanOption(option => option
+ .setName('value')
+ .setDescription('whether to leave when everyone else leaves')
+ .setRequired(true)))
+ .addSubcommand(subcommand => subcommand
.setName('get')
.setDescription('show all settings'));
@@ -70,6 +85,40 @@ export default class implements Command {
break;
}
+ case 'set-wait-after-queue-empty': {
+ const delay = interaction.options.getInteger('delay')!;
+
+ await prisma.setting.update({
+ where: {
+ guildId: interaction.guild!.id,
+ },
+ data: {
+ secondsToWaitAfterQueueEmpties: delay,
+ },
+ });
+
+ await interaction.reply('👍 wait delay updated');
+
+ break;
+ }
+
+ case 'set-leave-if-no-listeners': {
+ const value = interaction.options.getBoolean('value')!;
+
+ await prisma.setting.update({
+ where: {
+ guildId: interaction.guild!.id,
+ },
+ data: {
+ leaveIfNoListeners: value,
+ },
+ });
+
+ await interaction.reply('👍 leave setting updated');
+
+ break;
+ }
+
case 'get': {
const embed = new MessageEmbed().setTitle('Config');
@@ -82,6 +131,10 @@ export default class implements Command {
const settingsToShow = {
'Playlist Limit': config.playlistLimit,
Role: config.roleId ? `<@&${config.roleId}>` : 'not set',
+ 'Wait before leaving after queue empty': config.secondsToWaitAfterQueueEmpties === 0
+ ? 'never leave'
+ : `${config.secondsToWaitAfterQueueEmpties}s`,
+ 'Leave if there are no listeners': config.leaveIfNoListeners ? 'yes' : 'no',
};
let description = '';
diff --git a/src/events/voice-state-update.ts b/src/events/voice-state-update.ts
index 69b4504..60a40b1 100644
--- a/src/events/voice-state-update.ts
+++ b/src/events/voice-state-update.ts
@@ -3,15 +3,23 @@ import container from '../inversify.config.js';
import {TYPES} from '../types.js';
import PlayerManager from '../managers/player.js';
import {getSizeWithoutBots} from '../utils/channels.js';
+import {prisma} from '../utils/db.js';
-export default (oldState: VoiceState, _: VoiceState): void => {
+export default async (oldState: VoiceState, _: VoiceState): Promise<void> => {
const playerManager = container.get<PlayerManager>(TYPES.Managers.Player);
const player = playerManager.get(oldState.guild.id);
if (player.voiceConnection) {
const voiceChannel: VoiceChannel = oldState.guild.channels.cache.get(player.voiceConnection.joinConfig.channelId!) as VoiceChannel;
- if (!voiceChannel || getSizeWithoutBots(voiceChannel) === 0) {
+ const settings = await prisma.setting.findUnique({where: {guildId: player.guildId}});
+
+ if (!settings) {
+ throw new Error('Could not find settings for guild');
+ }
+
+ const {leaveIfNoListeners} = settings;
+ if (!voiceChannel || (getSizeWithoutBots(voiceChannel) === 0 && leaveIfNoListeners)) {
player.disconnect();
}
}
diff --git a/src/services/add-query-to-queue.ts b/src/services/add-query-to-queue.ts
index bb512c6..16d2f43 100644
--- a/src/services/add-query-to-queue.ts
+++ b/src/services/add-query-to-queue.ts
@@ -4,7 +4,7 @@ import {Except} from 'type-fest';
import shuffle from 'array-shuffle';
import {TYPES} from '../types.js';
import GetSongs from '../services/get-songs.js';
-import {QueuedSong} from './player.js';
+import {QueuedSong, STATUS} from './player.js';
import PlayerManager from '../managers/player.js';
import {prisma} from '../utils/db.js';
import {buildPlayingMessageEmbed} from '../utils/build-embed.js';
@@ -131,6 +131,9 @@ export default class AddQueryToQueue {
await interaction.editReply({
embeds: [buildPlayingMessageEmbed(player)],
});
+ } else if (player.status === STATUS.IDLE) {
+ // Player is idle, start playback instead
+ await player.play();
}
// Build response message
diff --git a/src/services/player.ts b/src/services/player.ts
index ce611e8..79ef2bb 100644
--- a/src/services/player.ts
+++ b/src/services/player.ts
@@ -8,6 +8,7 @@ import shuffle from 'array-shuffle';
import {AudioPlayer, AudioPlayerStatus, createAudioPlayer, createAudioResource, joinVoiceChannel, StreamType, VoiceConnection, VoiceConnectionStatus} from '@discordjs/voice';
import FileCacheProvider from './file-cache.js';
import debug from '../utils/debug.js';
+import {prisma} from '../utils/db.js';
export interface QueuedPlaylist {
title: string;
@@ -29,6 +30,7 @@ export interface QueuedSong {
export enum STATUS {
PLAYING,
PAUSED,
+ IDLE,
}
export interface PlayerEvents {
@@ -50,6 +52,7 @@ export default class {
private positionInSeconds = 0;
private readonly fileCache: FileCacheProvider;
+ private disconnectTimer: NodeJS.Timeout | null = null;
constructor(fileCache: FileCacheProvider, guildId: string) {
this.fileCache = fileCache;
@@ -131,6 +134,12 @@ export default class {
throw new Error('Queue empty.');
}
+ // Cancel any pending idle disconnection
+ if (this.disconnectTimer) {
+ clearInterval(this.disconnectTimer);
+ this.disconnectTimer = null;
+ }
+
// Resume from paused state
if (this.status === STATUS.PAUSED && currentSong.url === this.nowPlaying?.url) {
if (this.audioPlayer) {
@@ -206,12 +215,31 @@ export default class {
async forward(skip: number): Promise<void> {
this.manualForward(skip);
+ console.log(this.getCurrent());
+
try {
if (this.getCurrent() && this.status !== STATUS.PAUSED) {
await this.play();
} else {
- this.status = STATUS.PAUSED;
- this.disconnect();
+ this.audioPlayer?.stop();
+ this.status = STATUS.IDLE;
+
+ const settings = await prisma.setting.findUnique({where: {guildId: this.guildId}});
+
+ if (!settings) {
+ throw new Error('Could not find settings for guild');
+ }
+
+ const {secondsToWaitAfterQueueEmpties} = settings;
+ if (secondsToWaitAfterQueueEmpties !== 0) {
+ this.disconnectTimer = setTimeout(() => {
+ // Make sure we are not accidentally playing
+ // when disconnecting
+ if (this.status === STATUS.IDLE) {
+ this.disconnect();
+ }
+ }, secondsToWaitAfterQueueEmpties * 1000);
+ }
}
} catch (error: unknown) {
this.queuePosition--;