From fec70a25a1e5d425d64a2484aea9de22c1d397a6 Mon Sep 17 00:00:00 2001 From: Snowflake107 Date: Sun, 13 Jun 2021 18:28:36 +0545 Subject: [PATCH] implement looping system --- example/index.ts | 38 ++++++++++++++++++++++++++++++++++++-- src/Player.ts | 2 +- src/Structures/Queue.ts | 19 +++++++++++++++++-- src/Structures/Track.ts | 1 + src/types/types.ts | 6 ++++++ src/utils/Util.ts | 5 +++++ 6 files changed, 66 insertions(+), 5 deletions(-) diff --git a/example/index.ts b/example/index.ts index da746bc..6538b64 100644 --- a/example/index.ts +++ b/example/index.ts @@ -1,6 +1,6 @@ import { Client, GuildMember, Message, TextChannel } from "discord.js"; import { Player, Queue, Track } from "../src/index"; -import { QueryType } from "../src/types/types"; +import { QueryType, QueueRepeatMode } from "../src/types/types"; import { config } from "./config"; // use this in prod. // import { Player, Queue } from "discord-player"; @@ -76,6 +76,32 @@ client.on("message", async (message) => { } ] }, + { + name: "loop", + description: "Sets loop mode", + options: [ + { + name: "mode", + type: "INTEGER", + description: "Loop type", + required: true, + choices: [ + { + name: "Off", + value: 0 + }, + { + name: "Track", + value: 1 + }, + { + name: "Queue", + value: 2 + } + ] + } + ] + }, { name: "skip", description: "Skip to the current song" @@ -130,7 +156,7 @@ client.on("interaction", async (interaction) => { if (!searchResult.length) return void interaction.followUp({ content: "No results were found!" }); const queue = await player.createQueue(interaction.guild, { - metadata: interaction.channel + metadata: interaction.channel as TextChannel }); try { @@ -208,6 +234,14 @@ client.on("interaction", async (interaction) => { const queue = player.getQueue(interaction.guildID); if (!queue || !queue.playing) return void interaction.followUp({ content: "❌ | No music is being played!" }); return void interaction.followUp({ content: `🎶 | Current song: **${queue.current.title}**!` }); + } else if (interaction.commandName === "loop") { + await interaction.defer(); + const queue = player.getQueue(interaction.guildID); + if (!queue || !queue.playing) return void interaction.followUp({ content: "❌ | No music is being played!" }); + const loopMode = interaction.options.get("mode")!.value as QueueRepeatMode; + const success = queue.setRepeatMode(loopMode); + const mode = loopMode === QueueRepeatMode.TRACK ? "🔂" : loopMode === QueueRepeatMode.QUEUE ? "🔁" : "▶"; + return void interaction.followUp({ content: success ? `${mode} | Updated loop mode!` : "❌ | Could not update loop mode!" }); } else { interaction.reply({ content: "Unknown command!", diff --git a/src/Player.ts b/src/Player.ts index df299ce..910edb1 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -37,7 +37,7 @@ class DiscordPlayer extends EventEmitter { * @param {PlayerOptions} queueInitOptions Queue init options * @returns {Queue} */ - createQueue(guild: Guild, queueInitOptions?: PlayerOptions & { metadata?: any }) { + createQueue(guild: Guild, queueInitOptions?: PlayerOptions & { metadata?: T }): Queue { if (this.queues.has(guild.id)) return this.queues.get(guild.id) as Queue; const _meta = queueInitOptions.metadata; diff --git a/src/Structures/Queue.ts b/src/Structures/Queue.ts index 604f9c7..4a9cdfa 100644 --- a/src/Structures/Queue.ts +++ b/src/Structures/Queue.ts @@ -2,18 +2,21 @@ import { Guild, StageChannel, VoiceChannel } from "discord.js"; import { Player } from "../Player"; import { StreamDispatcher } from "../VoiceInterface/BasicStreamDispatcher"; import Track from "./Track"; -import { PlayerOptions, PlayOptions } from "../types/types"; +import { PlayerOptions, PlayOptions, QueueRepeatMode } from "../types/types"; import ytdl from "discord-ytdl-core"; import { AudioResource, StreamType } from "@discordjs/voice"; +import { Util } from "../utils/Util"; class Queue { public readonly guild: Guild; public readonly player: Player; public connection: StreamDispatcher; public tracks: Track[] = []; + public previousTracks: Track[] = []; public options: PlayerOptions; public playing = false; public metadata?: T = null; + public repeatMode: QueueRepeatMode = 0; constructor(player: Player, guild: Guild, options: PlayerOptions = {}) { this.player = player; @@ -99,6 +102,14 @@ class Queue { return this.connection.setVolume(amount); } + setRepeatMode(mode: QueueRepeatMode) { + if (![QueueRepeatMode.OFF, QueueRepeatMode.QUEUE, QueueRepeatMode.TRACK].includes(mode)) throw new Error(`Unknown repeat mode "${mode}"!`); + const prev = this.repeatMode; + if (mode === prev) return false; + this.repeatMode = mode; + return true; + } + get volume() { if (!this.connection) return 100; return this.connection.volume; @@ -109,6 +120,8 @@ class Queue { if (src && (this.playing || this.tracks.length) && !options.immediate) return this.addTrack(src); const track = options.filtersUpdate ? this.current : src ?? this.tracks.shift(); if (!track) return; + this.previousTracks = this.previousTracks.filter((x) => x._trackID !== track._trackID); + this.previousTracks.push(track); let stream; if (["youtube", "spotify"].includes(track.raw.source)) { stream = ytdl(track.raw.source === "spotify" ? track.raw.engine : track.url, { @@ -145,10 +158,12 @@ class Queue { this.playing = false; if (options.filtersUpdate) return; - if (!this.tracks.length) { + if (!this.tracks.length && this.repeatMode === QueueRepeatMode.OFF) { this.destroy(); this.player.emit("queueEnd", this); } else { + if (this.repeatMode === QueueRepeatMode.TRACK) return void this.play(Util.last(this.previousTracks), { immediate: true }); + if (this.repeatMode === QueueRepeatMode.QUEUE) this.tracks.push(Util.last(this.previousTracks)); const nextTrack = this.tracks.shift(); this.play(nextTrack, { immediate: true }); } diff --git a/src/Structures/Track.ts b/src/Structures/Track.ts index 7a5c5f6..f4aca3a 100644 --- a/src/Structures/Track.ts +++ b/src/Structures/Track.ts @@ -15,6 +15,7 @@ class Track { public requestedBy!: User; public fromPlaylist!: boolean; public readonly raw!: RawTrackData; + public readonly _trackID = Date.now(); /** * Track constructor diff --git a/src/types/types.ts b/src/types/types.ts index 4a118ac..f97994e 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -156,3 +156,9 @@ export interface SearchOptions { requestedBy: User; searchEngine?: QueryType; } + +export enum QueueRepeatMode { + OFF = 0, + TRACK = 1, + QUEUE = 2 +} diff --git a/src/utils/Util.ts b/src/utils/Util.ts index 8d54958..7411df1 100644 --- a/src/utils/Util.ts +++ b/src/utils/Util.ts @@ -29,6 +29,11 @@ class Util { .join(":"); return final.length <= 3 ? `0:${final.padStart(2, "0") || 0}` : final; } + + static last(arr: T[]): T { + if (!Array.isArray(arr)) return; + return arr[arr.length - 1]; + } } export { Util };