From 769f9da8a21ca467964142f57c79a0f482232900 Mon Sep 17 00:00:00 2001 From: Reynard G <40525094+Reynard-G@users.noreply.github.com> Date: Tue, 16 Jan 2024 19:44:44 -0600 Subject: [PATCH] Add /loop-queue command (#989) --- CHANGELOG.md | 3 +++ src/commands/loop-queue.ts | 42 ++++++++++++++++++++++++++++++++++++++ src/commands/loop.ts | 4 ++++ src/inversify.config.ts | 2 ++ src/services/player.ts | 12 +++++++++++ src/utils/build-embed.ts | 2 +- 6 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/commands/loop-queue.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index cc6f396..3161a6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Added `/loop-queue` + ## [2.4.4] - 2023-12-21 - Optimized Docker container to run JS code directly with node instead of yarn, npm and tsx. Reduces memory usage. diff --git a/src/commands/loop-queue.ts b/src/commands/loop-queue.ts new file mode 100644 index 0000000..47b9adf --- /dev/null +++ b/src/commands/loop-queue.ts @@ -0,0 +1,42 @@ +import {ChatInputCommandInteraction} from 'discord.js'; +import {TYPES} from '../types.js'; +import {inject, injectable} from 'inversify'; +import PlayerManager from '../managers/player.js'; +import Command from '.'; +import {SlashCommandBuilder} from '@discordjs/builders'; +import {STATUS} from '../services/player.js'; + +@injectable() +export default class implements Command { + public readonly slashCommand = new SlashCommandBuilder() + .setName('loop-queue') + .setDescription('toggle looping the entire queue'); + + public requiresVC = true; + + private readonly playerManager: PlayerManager; + + constructor(@inject(TYPES.Managers.Player) playerManager: PlayerManager) { + this.playerManager = playerManager; + } + + public async execute(interaction: ChatInputCommandInteraction): Promise { + const player = this.playerManager.get(interaction.guild!.id); + + if (player.status === STATUS.IDLE) { + throw new Error('no songs to loop!'); + } + + if (player.queueSize() < 2) { + throw new Error('not enough songs to loop a queue!'); + } + + if (player.loopCurrentSong) { + player.loopCurrentSong = false; + } + + player.loopCurrentQueue = !player.loopCurrentQueue; + + await interaction.reply((player.loopCurrentQueue ? 'looped queue :)' : 'stopped looping queue :(')); + } +} diff --git a/src/commands/loop.ts b/src/commands/loop.ts index 6618e84..f654237 100644 --- a/src/commands/loop.ts +++ b/src/commands/loop.ts @@ -27,6 +27,10 @@ export default class implements Command { throw new Error('no song to loop!'); } + if (player.loopCurrentQueue) { + player.loopCurrentQueue = false; + } + player.loopCurrentSong = !player.loopCurrentSong; await interaction.reply((player.loopCurrentSong ? 'looped :)' : 'stopped looping :(')); diff --git a/src/inversify.config.ts b/src/inversify.config.ts index 65a0258..d0694cf 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -21,6 +21,7 @@ import Config from './commands/config.js'; import Disconnect from './commands/disconnect.js'; import Favorites from './commands/favorites.js'; import ForwardSeek from './commands/fseek.js'; +import LoopQueue from './commands/loop-queue.js'; import Loop from './commands/loop.js'; import Move from './commands/move.js'; import Next from './commands/next.js'; @@ -68,6 +69,7 @@ container.bind(TYPES.Services.SpotifyAPI).to(SpotifyAPI).inSingleton Disconnect, Favorites, ForwardSeek, + LoopQueue, Loop, Move, Next, diff --git a/src/services/player.ts b/src/services/player.ts index 9754a42..c17a536 100644 --- a/src/services/player.ts +++ b/src/services/player.ts @@ -63,6 +63,7 @@ export default class { public status = STATUS.PAUSED; public guildId: string; public loopCurrentSong = false; + public loopCurrentQueue = false; private queue: QueuedSong[] = []; private queuePosition = 0; @@ -545,6 +546,17 @@ export default class { return; } + // Automatically re-add current song to queue + if (this.loopCurrentQueue && newState.status === AudioPlayerStatus.Idle && this.status === STATUS.PLAYING) { + const currentSong = this.getCurrent(); + + if (currentSong) { + this.add(currentSong); + } else { + throw new Error('No song currently playing.'); + } + } + if (newState.status === AudioPlayerStatus.Idle && this.status === STATUS.PLAYING) { await this.forward(1); } diff --git a/src/utils/build-embed.ts b/src/utils/build-embed.ts index e4159b6..d851e3f 100644 --- a/src/utils/build-embed.ts +++ b/src/utils/build-embed.ts @@ -46,7 +46,7 @@ const getPlayerUI = (player: Player) => { const button = player.status === STATUS.PLAYING ? '⏚ī¸' : 'â–ļī¸'; const progressBar = getProgressBar(15, position / song.length); const elapsedTime = song.isLive ? 'live' : `${prettyTime(position)}/${prettyTime(song.length)}`; - const loop = player.loopCurrentSong ? '🔁' : ''; + const loop = player.loopCurrentSong ? '🔂' : player.loopCurrentQueue ? '🔁' : ''; return `${button} ${progressBar} \`[${elapsedTime}]\` 🔉 ${loop}`; };