From d167e903168aea7069edbd32b63ec062c2a99b12 Mon Sep 17 00:00:00 2001 From: Taken Date: Fri, 9 Feb 2024 20:09:27 +0100 Subject: [PATCH] Added music commands --- src/commands/music.ts | 56 ++++++++++++++++++++++++++++ src/commands/music/leave.ts | 26 +++++++++++++ src/commands/music/play.ts | 41 ++++++++++++++++++++ src/commands/music/queue.ts | 30 +++++++++++++++ src/components/autocomplete/music.ts | 32 ++++++++++++++++ src/utils/Illegitimate.ts | 11 ++++-- 6 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 src/commands/music.ts create mode 100644 src/commands/music/leave.ts create mode 100644 src/commands/music/play.ts create mode 100644 src/commands/music/queue.ts create mode 100644 src/components/autocomplete/music.ts diff --git a/src/commands/music.ts b/src/commands/music.ts new file mode 100644 index 0000000..96cbed1 --- /dev/null +++ b/src/commands/music.ts @@ -0,0 +1,56 @@ +import { PermissionFlagsBits, SlashCommandBuilder } from "discord.js" +import { Command } from "interfaces" +import play from "./music/play" +import leave from "./music/leave" +import queue from "./music/queue" + +export = { + name: "music", + description: "Subcommands for music commands", + dev: true, + public: false, + subcommands: true, + + data: new SlashCommandBuilder() + .setName("music") + .setDescription("Subcommands for music commands") + .addSubcommand(subcommand => + subcommand + .setName("play") + .setDescription("Play a song") + .addStringOption(option => + option + .setName("query") + .setDescription("The song to play") + .setAutocomplete(true) + .setRequired(true))) + .addSubcommand(subcommand => + subcommand + .setName("queue") + .setDescription("Show the queue")) + .addSubcommand(subcommand => + subcommand + .setName("leave") + .setDescription("Leave the voice channel")) + .setDMPermission(false) + .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), + + async execute(interaction) { + const subcommand = interaction.options.getSubcommand() + + if (subcommand === "play") { + play(interaction) + return + } + + if (subcommand === "queue") { + queue(interaction) + return + } + + if (subcommand === "leave") { + leave(interaction) + return + } + } +} as Command \ No newline at end of file diff --git a/src/commands/music/leave.ts b/src/commands/music/leave.ts new file mode 100644 index 0000000..e6c61eb --- /dev/null +++ b/src/commands/music/leave.ts @@ -0,0 +1,26 @@ +import { embedColor } from "config/options" +import { useMainPlayer } from "discord-player" +import { ChatInputCommandInteraction } from "discord.js" + +export default async function leave(interaction: ChatInputCommandInteraction) { + const player = useMainPlayer() + const queue = player.queues.get(interaction.guildId!) + if (!queue) { + await interaction.reply({ + embeds: [{ + description: "There is no music playing", + color: embedColor + }] + }) + return + } + + + queue.delete() + await interaction.reply({ + embeds: [{ + description: "Left the voice channel", + color: embedColor + }] + }) +} \ No newline at end of file diff --git a/src/commands/music/play.ts b/src/commands/music/play.ts new file mode 100644 index 0000000..f7c24f6 --- /dev/null +++ b/src/commands/music/play.ts @@ -0,0 +1,41 @@ +import { embedColor } from "config/options" +import { useMainPlayer } from "discord-player" +import { ChatInputCommandInteraction, GuildMember } from "discord.js" + +export default async function play(interaction: ChatInputCommandInteraction) { + await interaction.deferReply() + const query = interaction.options.getString("query")! + const channel = (interaction.member as GuildMember).voice.channel + const player = useMainPlayer() + + if (!channel) { + await interaction.editReply({ + embeds: [{ + description: "You need to be in a voice channel to play music", + color: embedColor + }] + }) + return + } + + const { track } = await player.play(channel, query, { + requestedBy: interaction.user, + nodeOptions: { + volume: 50, + } + }) + + await interaction.editReply({ + embeds: [{ + description: `Playing [${track.title}](${track.url})`, + thumbnail: { + url: track.thumbnail + }, + color: embedColor, + footer: { + text: track.duration + " minutes", + icon_url: interaction.user.avatarURL()! + } + }] + }) +} \ No newline at end of file diff --git a/src/commands/music/queue.ts b/src/commands/music/queue.ts new file mode 100644 index 0000000..d9e1fd7 --- /dev/null +++ b/src/commands/music/queue.ts @@ -0,0 +1,30 @@ +import { embedColor } from "config/options" +import { useMainPlayer } from "discord-player" +import { ChatInputCommandInteraction } from "discord.js" + +export default async function queue(interaction: ChatInputCommandInteraction) { + await interaction.deferReply() + const player = useMainPlayer() + + const queue = player.queues.get(interaction.guildId!) + + if (!queue) { + await interaction.editReply("There is nothing playing") + return + } + + const currentSong = queue.currentTrack + + const nowPlaying = `Now playing: [${currentSong?.title}](${currentSong?.url})` + + const tracks = queue.tracks.map((track, index) => { + return `${index + 1}. [${track.title}](${track.url})` + }) + + await interaction.editReply({ + embeds: [{ + description: nowPlaying + "\n\n" + tracks.join("\n"), + color: embedColor + }] + }) +} \ No newline at end of file diff --git a/src/components/autocomplete/music.ts b/src/components/autocomplete/music.ts new file mode 100644 index 0000000..241b135 --- /dev/null +++ b/src/components/autocomplete/music.ts @@ -0,0 +1,32 @@ +import { QueryType, useMainPlayer } from "discord-player" +import { Autocomplete } from "interfaces" + +export = { + name: "music", + description: "Music", + + async execute(interaction) { + const focusedOption = interaction.options.getFocused(true) + if (interaction.options.getSubcommand() !== "play" && focusedOption.name !== "query") return + + if (focusedOption.value === "") { + await interaction.respond([{ + name: "Please start typing a song to play", + value: "none" + }]) + return + } + + const player = useMainPlayer() + const { tracks } = await player.search(focusedOption.value, { + searchEngine: QueryType.YOUTUBE_SEARCH + }) + + const results = tracks.map(track => ({ + name: track.title, + value: track.url + })) + + await interaction.respond(results.slice(0, 25)).catch() + } +} as Autocomplete \ No newline at end of file diff --git a/src/utils/Illegitimate.ts b/src/utils/Illegitimate.ts index 4a210c1..e72b483 100644 --- a/src/utils/Illegitimate.ts +++ b/src/utils/Illegitimate.ts @@ -4,8 +4,12 @@ import { Redis } from "ioredis" import env from "utils/Env" import { connect } from "mongoose" import loadAllEvents from "./Events" +import { Player } from "discord-player" + const client = new Client() const redis = new Redis(env.prod.redisURI!) +const player = new Player(client) + let ft: "js" | "ts" if (process.env.NODE_ENV === "dev" && process.env.TYPESCRIPT === "true") { ft = "ts" @@ -15,10 +19,11 @@ if (process.env.NODE_ENV === "dev" && process.env.TYPESCRIPT === "true") { class Illegitimate { async start() { - this.init() + await this.init() loadAllEvents(client, ft) - client.start() - this.databases() + await player.extractors.loadDefault() + await client.start() + await this.databases() } private async databases() {