Skip to content
This repository has been archived by the owner on Oct 12, 2024. It is now read-only.

Commit

Permalink
Int-Fun-Music: Initial code (mostly reused from v2)
Browse files Browse the repository at this point in the history
  • Loading branch information
RadiatedExodus committed Jun 29, 2024
1 parent 9f980bd commit 47aa056
Show file tree
Hide file tree
Showing 5 changed files with 549 additions and 280 deletions.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,20 @@
"typescript": "^5.3.3"
},
"dependencies": {
"@discordjs/opus": "^0.9.0",
"@discord-player/extractor": "^4.4.7",
"@discordjs/voice": "^0.16.1",
"@prisma/client": "^5.9.1",
"bufferutil": "^4.0.8",
"chalk": "^5.3.0",
"discord-player": "^6.6.10",
"discord.js": "^14.14.1",
"dotenv": "^16.4.2",
"holodex.js": "^2.0.5",
"mediaplex": "^0.0.9",
"moment": "^2.30.1",
"noblox.js": "^4.15.1",
"utf-8-validate": "^6.0.3",
"youtube-ext": "^1.1.25",
"zlib-sync": "^0.1.9"
}
}
7 changes: 7 additions & 0 deletions src/classes/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Client, ClientOptions } from "discord.js";
import { config } from "dotenv";
import { PrismaClient } from "@prisma/client";
import { HolodexApiClient } from "holodex.js";
import { Player } from "discord-player";
import { lyricsExtractor } from "@discord-player/extractor";

import Logging from "./logging.js";
import MeteoriumInteractionManager from "../interactions/index.js";
Expand Down Expand Up @@ -31,6 +33,8 @@ export default class MeteoriumClient extends Client<true> {
public interactions = new MeteoriumInteractionManager(this);
public events = new MeteoriumEventManager(this);
public holodex = new HolodexApiClient({ apiKey: this.config.HolodexApiKey });
public player = new Player(this);
public playerLyricsExtractor = lyricsExtractor(this.config.GeniusApiKey);

public constructor(options: ClientOptions) {
super(options);
Expand All @@ -50,6 +54,9 @@ export default class MeteoriumClient extends Client<true> {
// Hook events
this.events.hook();

// Load discord-player default extractors
this.player.extractors.loadDefault();

// Login
loginNS.info("Logging in to Discord");
return super.login(this.config.BotToken);
Expand Down
209 changes: 209 additions & 0 deletions src/interactions/commands/fun/music.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { SlashCommandBuilder } from "discord.js";
import type { MeteoriumChatCommand } from "../../index.js";
import { GuildFeatures } from "@prisma/client";
import MeteoriumEmbedBuilder from "../../../classes/embedBuilder.js";

export const Command: MeteoriumChatCommand = {
interactionData: new SlashCommandBuilder()
.setName("music")
.setDescription("Meteorium music functionality")
.addSubcommand((subcommand) =>
subcommand
.setName("play")
.setDescription("Play sound/music from YouTube")
.addStringOption((option) =>
option
.setName("query")
.setDescription("A link/search term to the target YouTube video")
.setRequired(true),
),
)
.addSubcommand((subcommand) => subcommand.setName("stop").setDescription("Stop and disconnect the bot"))
.addSubcommand((subcommand) => subcommand.setName("pause").setDescription("Pause the current track"))
.addSubcommand((subcommand) => subcommand.setName("resume").setDescription("Resumes the current track"))
.addSubcommand((subcommand) =>
subcommand
.setName("volume")
.setDescription("Set the volume of the music player")
.addNumberOption((option) =>
option
.setName("percent")
.setDescription(
"Volume percentage (1-100), if not specified then will reply with current volume.",
)
.setRequired(false),
),
)
.addSubcommand((subcommand) =>
subcommand.setName("queue").setDescription("Get the queue of songs in this server"),
)
.addSubcommand((subcommand) => subcommand.setName("clearqueue").setDescription("Clears the queue"))
.addSubcommand((subcommand) => subcommand.setName("skip").setDescription("Skips the current track"))
.addSubcommand((subcommand) => subcommand.setName("back").setDescription("Goes back to the previous track"))
.setDMPermission(false),
requiredFeature: GuildFeatures.Music,
async callback(interaction, client) {
await interaction.deferReply({ ephemeral: false });

if (!interaction.guild.available)
return await interaction.editReply({
content: "Guild/server not available. (Is there a outage at the moment?)",
});

const subcommand = interaction.options.getSubcommand();
let playerNode = client.player.nodes.get(interaction.guildId);

switch (subcommand) {
case "play": {
const query = interaction.options.getString("query", true);
const channel = interaction.member.voice.channel;
if (!channel)
return await interaction.reply({
content: "You are not in a voice channel.",
});

if (
interaction.guild.members.me?.voice.channelId &&
interaction.member.voice.channelId !== interaction.guild.members.me?.voice.channelId
)
return await interaction.reply({
content: "You are not in the same voice channel.",
});

const search = await client.player.search(query, {
requestedBy: interaction.user,
});
if (!search.hasTracks())
return await interaction.reply({
content: "No tracks found.",
});

await client.player.play(channel, search, {
nodeOptions: {
metadata: interaction,
selfDeaf: true,
leaveOnEmpty: true,
leaveOnEnd: true,
},
});

return await interaction.editReply({
content: `Now playing: ${query}`,
});
}
case "stop": {
if (!playerNode)
return await interaction.editReply({
content: "The bot isn't connected to any voice channel.",
});
playerNode.delete();
return await interaction.editReply({
content: "Stopped and disconnected the bot.",
});
}
case "pause": {
if (!playerNode)
return await interaction.editReply({
content: "The bot isn't connected to any voice channel.",
});
playerNode.node.pause();
return await interaction.editReply({
content: "Paused the queue.",
});
}
case "resume": {
if (!playerNode)
return await interaction.editReply({
content: "The bot isn't connected to any voice channel.",
});
playerNode.node.resume();
return await interaction.editReply({
content: "Resumed the queue.",
});
}
case "volume": {
const percent = interaction.options.getNumber("percent", false);
if (!playerNode)
return await interaction.editReply({
content: "The bot isn't connected to any voice channel.",
});
if (!percent)
return await interaction.editReply({
content: `The current volume is ${playerNode.node.volume}%`,
});
if (percent < 0 || percent > 100)
return await interaction.editReply({
content: "The volume must be betweeen 1% and 100%",
});
playerNode.node.setVolume(percent);
return await interaction.editReply({
content: `Set the volume to ${percent}%`,
});
}
case "queue": {
if (!playerNode)
return await interaction.editReply({
content: "The bot isn't connected to any voice channel.",
});
if (!playerNode || (!playerNode.node.isPlaying() && playerNode.tracks.size === 0))
return await interaction.editReply({
content: "The queue is empty.",
});

const tracks = playerNode.tracks
.toArray()
.slice(0, 25)
.map((track, i) => {
return `${i + 1}. [**${track.title}**](${track.url}) - ${track.requestedBy}`;
});

const embed = new MeteoriumEmbedBuilder(interaction.user)
.setTitle("Player queue")
.setDescription(`(Only the first 25 tracks are listed)\n\n${tracks.join("\n")}`);

return await interaction.editReply({ embeds: [embed] });
}
case "clearqueue": {
if (!playerNode)
return await interaction.editReply({
content: "The bot isn't connected to any voice channel or the queue is empty.",
});
playerNode.clear();
return await interaction.editReply({
content: "Cleared the queue.",
});
}
case "skip": {
if (!playerNode)
return await interaction.editReply({
content: "The bot isn't connected to any voice channel.",
});
const AmountOfTracksToSkip = interaction.options.getNumber("amountoftracks", false);
if (!AmountOfTracksToSkip) {
playerNode.node.skip();
return interaction.editReply({
content: "Skipped the current track.",
});
} else {
playerNode.node.skipTo(AmountOfTracksToSkip);
return interaction.editReply({
content: `Skipped ${AmountOfTracksToSkip} tracks.`,
});
}
}
case "back": {
if (!playerNode)
return await interaction.editReply({
content: "The bot isn't connected to any voice channel.",
});
playerNode.history.back();
return await interaction.editReply({
content: "Now playing the previous track.",
});
}
default: {
return await interaction.editReply({ content: `BUG: unknown subcommand ${subcommand}` });
}
}
},
};
3 changes: 3 additions & 0 deletions src/interactions/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ export * as Purge from "./moderation/purge.js";

// Management
export * as Settings from "./management/settings.js";

// Fun
export * as Music from "./fun/music.js";
Loading

0 comments on commit 47aa056

Please sign in to comment.