diff --git a/src/discord/MusicPlayer/Loop.ts b/src/discord/MusicPlayer/Loop.ts new file mode 100644 index 0000000..5d1f8c3 --- /dev/null +++ b/src/discord/MusicPlayer/Loop.ts @@ -0,0 +1,109 @@ +import DiscordModule, { HybridInteractionMessage } from "../../utils/DiscordModule"; + +import { Message, CommandInteraction, Interaction, MessageActionRow, ButtonInteraction, MessageSelectMenu, MessageSelectOptionData } from "discord.js"; +import { makeErrorEmbed, makeSuccessEmbed, sendHybridInteractionMessageResponse, makeInfoEmbed } from "../../utils/DiscordMessage"; +import DiscordMusicPlayer, { DiscordMusicPlayerLoopMode } from "../../providers/DiscordMusicPlayer"; + +const EMBEDS = { + INVALID_LOOP_MODE: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: `Invalid loop mode`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + LOOP_STATUS: (data: Message | Interaction, loopMode: DiscordMusicPlayerLoopMode) => { + let embed = makeInfoEmbed({ + title: `Current loop mode is ${loopMode}`, + description: "Available loop mode: ``None`` ``Current``", + user: (data instanceof Interaction) ? data.user : data.author + }); + return embed; + }, + LOOP_SET: (data: Message | Interaction, loopMode: DiscordMusicPlayerLoopMode) => { + let embed = makeSuccessEmbed({ + title: `Loop mode is now set to ${loopMode}`, + user: (data instanceof Interaction) ? data.user : data.author + }); + return embed; + }, + USER_NOT_IN_VOICECHANNEL: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: `You need to be in the voice channel first!`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + USER_NOT_IN_SAME_VOICECHANNEL: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: `You are not in the same voice channel!`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + NO_MUSIC_PLAYING: (data: Message | Interaction) => { + return makeErrorEmbed({ + title: `There are no music playing`, + user: (data instanceof Interaction) ? data.user : data.author + }); + } +} +export default class Loop extends DiscordModule { + + public id = "Discord_MusicPlayer_Loop"; + public commands = ["loop"]; + public commandInteractionName = "loop"; + + async GuildOnModuleCommand(args: any, message: Message) { + await this.run(new HybridInteractionMessage(message), args); + } + + async GuildModuleCommandInteractionCreate(interaction: CommandInteraction) { + await this.run(new HybridInteractionMessage(interaction), interaction.options); + } + + async run(data: HybridInteractionMessage, args: any) { + + const guild = data.getGuild(); + const user = data.getUser(); + const member = data.getMember(); + const channel = data.getChannel(); + + if (!guild || !user || !member || !channel) return; + + let query; + + if (data.isMessage()) { + if (args.length !== 0) + query = args.join(' '); + } + else if (data.isSlashCommand()) + query = args.getSubcommand(); + + const voiceChannel = member.voice.channel; + + if (!voiceChannel) + return await sendHybridInteractionMessageResponse(data, { embeds: [EMBEDS.USER_NOT_IN_VOICECHANNEL(data.getRaw())] }); + + const instance = DiscordMusicPlayer.getGuildInstance(guild.id); + + if (!instance) + return await sendHybridInteractionMessageResponse(data, { embeds: [EMBEDS.NO_MUSIC_PLAYING(data.getRaw())] }); + + if (instance.voiceChannel.id !== voiceChannel.id) + return await sendHybridInteractionMessageResponse(data, { embeds: [EMBEDS.USER_NOT_IN_SAME_VOICECHANNEL(data.getRaw())] }, true); + + if (instance.queue.track.length === 0) + return await sendHybridInteractionMessageResponse(data, { embeds: [EMBEDS.NO_MUSIC_PLAYING(data.getRaw())] }); + + if (!query) + return await sendHybridInteractionMessageResponse(data, { embeds: [EMBEDS.LOOP_STATUS(data.getRaw(), instance.getLoopMode())] }); + + let enumKey = Object.keys(DiscordMusicPlayerLoopMode)[Object.values(DiscordMusicPlayerLoopMode).indexOf(query.toLowerCase())]; + + if(enumKey) { + instance.setLoopMode(DiscordMusicPlayerLoopMode[enumKey as keyof typeof DiscordMusicPlayerLoopMode]); + return await sendHybridInteractionMessageResponse(data, { embeds: [EMBEDS.LOOP_SET(data.getRaw(), instance.getLoopMode())] }); + } + + return await sendHybridInteractionMessageResponse(data, { embeds: [EMBEDS.INVALID_LOOP_MODE(data.getRaw())] }); + } + +} \ No newline at end of file diff --git a/src/discord/MusicPlayer/Queue.ts b/src/discord/MusicPlayer/Queue.ts index 9eea4fe..e0c66e1 100644 --- a/src/discord/MusicPlayer/Queue.ts +++ b/src/discord/MusicPlayer/Queue.ts @@ -3,10 +3,13 @@ import DiscordModule, { HybridInteractionMessage } from "../../utils/DiscordModu import { Message, CommandInteraction, Interaction, TextChannel } from "discord.js"; import { makeErrorEmbed, sendHybridInteractionMessageResponse, makeInfoEmbed } from "../../utils/DiscordMessage"; -import DiscordMusicPlayer, { Queue } from "../../providers/DiscordMusicPlayer"; +import DiscordMusicPlayer, { DiscordMusicPlayerInstance, DiscordMusicPlayerLoopMode } from "../../providers/DiscordMusicPlayer"; const EMBEDS = { - QUEUE: (data: Message | Interaction, queue: Queue) => { + QUEUE: (data: Message | Interaction, instance: DiscordMusicPlayerInstance) => { + + const queue = instance.queue; + if(!queue.track[0]) return makeInfoEmbed({ title: `Queue`, @@ -18,7 +21,7 @@ const EMBEDS = { let queueString = `1. [${queue.track[0].title}](${queue.track[0].url})` return makeInfoEmbed({ title: `Queue`, - description: `Now playing: [${queue.track[0].title}](${queue.track[0].url})\n\nThere are ${queue.track.length} song in the queue!\n${queueString}`, + description: `Now playing${instance.getLoopMode() === DiscordMusicPlayerLoopMode.Current ? " (Looping current)" : ""}: [${queue.track[0].title}](${queue.track[0].url})\n\nThere are ${queue.track.length} song in the queue!\n${queueString}`, user: (data instanceof Interaction) ? data.user : data.author }); } @@ -27,14 +30,14 @@ const EMBEDS = { let queueString = first10.map((track, index) => `${index + 1}. [${track.title}](${track.url})`).join("\n"); return makeInfoEmbed({ title: `Queue`, - description: `Now playing: [${queue.track[0].title}](${queue.track[0].url})\nUpcoming song: [${queue.track[1].title}](${queue.track[1].url})\n\nThere are ${queue.track.length} songs in the queue!\n${queueString}\n${queue.track.length > 10 ? `...${queue.track.length - 10} more songs` : ""}`, + description: `Now playing${instance.getLoopMode() === DiscordMusicPlayerLoopMode.Current ? " (Looping current)" : ""}: [${queue.track[0].title}](${queue.track[0].url})\nUpcoming song: [${queue.track[1].title}](${queue.track[1].url})\n\nThere are ${queue.track.length} songs in the queue!\n${queueString}\n${queue.track.length > 10 ? `...${queue.track.length - 10} more songs` : ""}`, user: (data instanceof Interaction) ? data.user : data.author }); } else { let queueString = queue.track.map((track, index) => `${index + 1}. [${track.title}](${track.url})`).join("\n"); return makeInfoEmbed({ title: `Queue`, - description: `Now playing: [${queue.track[0].title}](${queue.track[0].url})\nUpcoming song: [${queue.track[1].title}](${queue.track[1].url})\n\nThere are ${queue.track.length} songs in the queue!\n${queueString}`, + description: `Now playing${instance.getLoopMode() === DiscordMusicPlayerLoopMode.Current ? " (Looping current)" : ""}: [${queue.track[0].title}](${queue.track[0].url})\nUpcoming song: [${queue.track[1].title}](${queue.track[1].url})\n\nThere are ${queue.track.length} songs in the queue!\n${queueString}`, user: (data instanceof Interaction) ? data.user : data.author }); } @@ -71,7 +74,7 @@ export default class QueueCommand extends DiscordModule { const instance = DiscordMusicPlayer.getGuildInstance(guild.id); if(!instance) return await sendHybridInteractionMessageResponse(data, { embeds: [EMBEDS.NO_MUSIC_PLAYING(data.getRaw())] }); - await sendHybridInteractionMessageResponse(data, { embeds:[EMBEDS.QUEUE(data.getRaw(), instance.queue)] }); + await sendHybridInteractionMessageResponse(data, { embeds:[EMBEDS.QUEUE(data.getRaw(), instance)] }); return; } diff --git a/src/providers/Discord.ts b/src/providers/Discord.ts index d282ee7..9c2de5f 100644 --- a/src/providers/Discord.ts +++ b/src/providers/Discord.ts @@ -23,6 +23,7 @@ import Discord_MusicPlayer_Leave from "../discord/MusicPlayer/Leave"; import Discord_MusicPlayer_Queue from "../discord/MusicPlayer/Queue"; import Discord_MusicPlayer_Search from "../discord/MusicPlayer/Search"; import Discord_MusicPlayer_NowPlaying from "../discord/MusicPlayer/NowPlaying"; +import Discord_MusicPlayer_Loop from "../discord/MusicPlayer/Loop"; import Discord_Developer_ServiceAnnouncement from "../discord/developer/ServiceAnnouncement"; import Discord_Developer_Debug from "../discord/developer/Debug"; @@ -67,6 +68,7 @@ class Discord { new Discord_MusicPlayer_Leave(), new Discord_MusicPlayer_Queue(), new Discord_MusicPlayer_Search(), + new Discord_MusicPlayer_Loop(), new Discord_MembershipScreening(), diff --git a/src/providers/DiscordMusicPlayer.ts b/src/providers/DiscordMusicPlayer.ts index ee182c7..b9ebd7f 100644 --- a/src/providers/DiscordMusicPlayer.ts +++ b/src/providers/DiscordMusicPlayer.ts @@ -54,6 +54,11 @@ export class VoiceDisconnectedEvent { this.instance = instance; } } + +export enum DiscordMusicPlayerLoopMode { + None = "none", + Current = "current" +} export class DiscordMusicPlayerInstance { public queue: Queue; public player: AudioPlayer; @@ -61,6 +66,8 @@ export class DiscordMusicPlayerInstance { public voiceChannel: (VoiceChannel | StageChannel); public voiceConnection?: VoiceConnection; + public loopMode: DiscordMusicPlayerLoopMode = DiscordMusicPlayerLoopMode.None; + public readonly events: EventEmitter; constructor({ voiceChannel }: { voiceChannel: (VoiceChannel | StageChannel) }) { @@ -77,12 +84,21 @@ export class DiscordMusicPlayerInstance { this.player.on(AudioPlayerStatus.Idle, async (oldStage, newStage) => { //The player stopped if (newStage.status === AudioPlayerStatus.Idle && oldStage.status !== AudioPlayerStatus.Idle) { + + // Loop mode is set to current song + if (this.loopMode === DiscordMusicPlayerLoopMode.Current) { + this.playTrack(this.queue.track[0]); + return; + } + + // There are more songs in the queue, remove finished song and play the next one if (this.queue.track.length !== 0) { this.queue.track.shift(); if (this.queue.track.length > 0) { this.playTrack(this.queue.track[0]); } } + } }); @@ -196,6 +212,14 @@ export class DiscordMusicPlayerInstance { } } + public setLoopMode(mode: DiscordMusicPlayerLoopMode) { + this.loopMode = mode; + } + + public getLoopMode() { + return this.loopMode; + } + public async destroy() { await this.leaveVoiceChannel();