diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d95b41..1e13119 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +- `/loop` command that plays the current song on loop + ## [2.0.4] - 2022-05-16 ### Fixed diff --git a/src/commands/loop.ts b/src/commands/loop.ts new file mode 100644 index 0000000..3b609a5 --- /dev/null +++ b/src/commands/loop.ts @@ -0,0 +1,34 @@ +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'; + +@injectable() +export default class implements Command { + public readonly slashCommand = new SlashCommandBuilder() + .setName('loop') + .setDescription('toggle looping the current song'); + + 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 song to loop!'); + } + + 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 f420071..002d72c 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -37,6 +37,7 @@ import Unskip from './commands/unskip.js'; import ThirdParty from './services/third-party.js'; import FileCacheProvider from './services/file-cache.js'; import KeyValueCacheProvider from './services/key-value-cache.js'; +import Loop from './commands/loop'; const container = new Container(); @@ -79,6 +80,7 @@ container.bind(TYPES.Services.SpotifyAPI).to(SpotifyAPI).inSingleton Next, Stop, Unskip, + Loop, ].forEach(command => { container.bind(TYPES.Command).to(command).inSingletonScope(); }); diff --git a/src/services/player.ts b/src/services/player.ts index c8a95a4..0af6fec 100644 --- a/src/services/player.ts +++ b/src/services/player.ts @@ -60,6 +60,7 @@ export default class { public voiceConnection: VoiceConnection | null = null; public status = STATUS.PAUSED; public guildId: string; + public loopCurrentSong = false; private queue: QueuedSong[] = []; private queuePosition = 0; @@ -69,7 +70,6 @@ export default class { private lastSongURL = ''; private positionInSeconds = 0; - private readonly fileCache: FileCacheProvider; private disconnectTimer: NodeJS.Timeout | null = null; @@ -92,6 +92,7 @@ export default class { this.pause(); } + this.loopCurrentSong = false; this.voiceConnection.destroy(); this.audioPlayer?.stop(); @@ -523,6 +524,11 @@ export default class { private async onAudioPlayerIdle(_oldState: AudioPlayerState, newState: AudioPlayerState): Promise { // Automatically advance queued song at end + if (this.loopCurrentSong && newState.status === AudioPlayerStatus.Idle && this.status === STATUS.PLAYING) { + await this.seek(0); + return; + } + 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 3ee17ce..e4159b6 100644 --- a/src/utils/build-embed.ts +++ b/src/utils/build-embed.ts @@ -46,8 +46,8 @@ 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)}`; - - return `${button} ${progressBar} \`[${elapsedTime}]\` 🔉`; + const loop = player.loopCurrentSong ? '🔁' : ''; + return `${button} ${progressBar} \`[${elapsedTime}]\` 🔉 ${loop}`; }; export const buildPlayingMessageEmbed = (player: Player): EmbedBuilder => { @@ -119,7 +119,7 @@ export const buildQueueEmbed = (player: Player, page: number): EmbedBuilder => { } message - .setTitle(player.status === STATUS.PLAYING ? 'Now Playing' : 'Queued songs') + .setTitle(player.status === STATUS.PLAYING ? `Now Playing ${player.loopCurrentSong ? '(loop on)' : ''}` : 'Queued songs') .setColor(player.status === STATUS.PLAYING ? 'DarkGreen' : 'NotQuiteBlack') .setDescription(description) .addFields([{name: 'In queue', value: getQueueInfo(player), inline: true}, {