diff --git a/src/bot/commands.ts b/src/bot/commands.ts index 5a9efeb..b984e4f 100644 --- a/src/bot/commands.ts +++ b/src/bot/commands.ts @@ -81,13 +81,22 @@ function activeSession(messageId: string | undefined): SearchSession | null { return session; } -/** Resolves a reaction on a results message into the track it stands for. */ +/** + * Resolves a reaction on a results message into the track it stands for, and + * spends the session: the list is answered once, so a second reaction on the + * same message does nothing and the message itself can go away. + */ export function trackForReaction(messageId: string, userId: string, emoji: string): Track | null { const session = activeSession(messageId); // Only the person who searched picks; otherwise anyone could hijack the list. if (!session || session.userId !== userId) return null; const index = CHOICE_EMOJI.indexOf(emoji); - return index === -1 ? null : (session.tracks[index] ?? null); + const track = index === -1 ? null : (session.tracks[index] ?? null); + if (track) { + sessionsByMessage.delete(messageId); + latestByUser.delete(`${session.channelId}:${session.userId}`); + } + return track; } function added(track: Track, startedNow: boolean, position: number): string { diff --git a/src/bot/index.ts b/src/bot/index.ts index 09fd01c..c8379a7 100644 --- a/src/bot/index.ts +++ b/src/bot/index.ts @@ -66,6 +66,10 @@ async function handleChoice( const username = message.server?.getMember(userId)?.nickname ?? track.requestedBy.username; try { const text = await enqueueChoice(manager, serverId, message.channelId, { id: userId, username }, track); + // The list has served its purpose; leaving it behind only clutters the channel. + await message.delete().catch((err: unknown) => { + log.debug({ err }, "could not delete the search results message"); + }); await message.channel?.sendMessage(text); } catch (err) { if (err instanceof UserFacingError) { diff --git a/src/core/player.ts b/src/core/player.ts index e71c922..88a386b 100644 --- a/src/core/player.ts +++ b/src/core/player.ts @@ -306,7 +306,7 @@ export class GuildPlayer extends EventEmitter { /** Starts playback if nothing is currently playing. */ async ensurePlaying(): Promise { if (this.current || this.status === "buffering" || this.status === "connecting") return; - await this.advance(false); + await this.advance(false, false); } private async startPlayback(track: Track, seekSeconds = 0): Promise { @@ -425,7 +425,7 @@ export class GuildPlayer extends EventEmitter { } /** Moves to the next track. `skipLoop` ignores per-track looping (used by skip). */ - private async advance(skipLoop: boolean): Promise { + private async advance(skipLoop: boolean, announce = true): Promise { const finished = this.current; this.current = null; // The track has to go with the sound: a published video track keeps showing @@ -452,14 +452,16 @@ export class GuildPlayer extends EventEmitter { } await this.startPlayback(next); - this.notify(`▶️ Сейчас играет: **${next.title}**`); + // A command that started this track has already said so; announcing again + // would double every line in the channel. + if (announce) this.notify(`▶️ Сейчас играет: **${next.title}**`); } async skip(count = 1): Promise { if (!this.current && this.queue.length === 0) throw new UserFacingError("Нечего пропускать"); for (let i = 1; i < count; i += 1) this.queue.shift(); this.teardownPlayback(); - await this.advance(true); + await this.advance(true, false); return this.current; } @@ -558,7 +560,7 @@ export class GuildPlayer extends EventEmitter { if (tracks.length === 0) return; this.queue.unshift(...tracks); this.teardownPlayback(); - await this.advance(true); + await this.advance(true, false); } // ---------------------------------------------------------- housekeeping ---