Add music player functionality and update dependencies for improved performance
Integrate discord-player and extractors, update package.json dependencies, and add music command functionality to bot.js and music.js. Replit-Commit-Author: Agent Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 85c6eaeb-53ee-4ce1-abcf-e58baf3f4df1 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/Mc7EIoi Replit-Helium-Checkpoint-Created: true
This commit is contained in:
parent
785ce3e31a
commit
ba1d4c90da
6 changed files with 5045 additions and 3 deletions
|
|
@ -9,6 +9,8 @@ const {
|
|||
PermissionFlagsBits,
|
||||
} = require("discord.js");
|
||||
const { createClient } = require("@supabase/supabase-js");
|
||||
const { Player } = require("discord-player");
|
||||
const { DefaultExtractors } = require("@discord-player/extractor");
|
||||
const http = require("http");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
|
@ -53,6 +55,63 @@ const client = new Client({
|
|||
],
|
||||
});
|
||||
|
||||
// =============================================================================
|
||||
// DISCORD PLAYER SETUP
|
||||
// =============================================================================
|
||||
|
||||
const player = new Player(client, {
|
||||
ytdlOptions: {
|
||||
quality: 'highestaudio',
|
||||
highWaterMark: 1 << 25
|
||||
}
|
||||
});
|
||||
|
||||
(async () => {
|
||||
await player.extractors.loadMulti(DefaultExtractors);
|
||||
console.log('[Music] Extractors loaded');
|
||||
})();
|
||||
|
||||
player.events.on('playerStart', (queue, track) => {
|
||||
const channel = queue.metadata?.channel;
|
||||
if (channel) {
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0x5865f2)
|
||||
.setTitle('Now Playing')
|
||||
.setDescription(`**[${track.title}](${track.url})**`)
|
||||
.setThumbnail(track.thumbnail)
|
||||
.addFields(
|
||||
{ name: 'Duration', value: track.duration, inline: true },
|
||||
{ name: 'Author', value: track.author, inline: true }
|
||||
);
|
||||
channel.send({ embeds: [embed] }).catch(() => {});
|
||||
}
|
||||
});
|
||||
|
||||
player.events.on('audioTrackAdd', (queue, track) => {
|
||||
console.log(`[Music] Track added: ${track.title}`);
|
||||
});
|
||||
|
||||
player.events.on('playerError', (queue, error) => {
|
||||
console.error(`[Music] Player error: ${error.message}`);
|
||||
});
|
||||
|
||||
player.events.on('error', (queue, error) => {
|
||||
console.error(`[Music] General error: ${error.message}`);
|
||||
});
|
||||
|
||||
player.events.on('emptyQueue', (queue) => {
|
||||
const channel = queue.metadata?.channel;
|
||||
if (channel) {
|
||||
channel.send({
|
||||
embeds: [new EmbedBuilder()
|
||||
.setColor(0x5865f2)
|
||||
.setDescription('Queue finished. Leaving voice channel...')]
|
||||
}).catch(() => {});
|
||||
}
|
||||
});
|
||||
|
||||
console.log('[Music] Discord Player initialized');
|
||||
|
||||
// =============================================================================
|
||||
// SUPABASE SETUP (Modified: Now optional)
|
||||
// =============================================================================
|
||||
|
|
|
|||
332
aethex-bot/commands/music.js
Normal file
332
aethex-bot/commands/music.js
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
|
||||
const { useMainPlayer, useQueue } = require('discord-player');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('music')
|
||||
.setDescription('Music player controls')
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('play')
|
||||
.setDescription('Play a song or playlist')
|
||||
.addStringOption(opt =>
|
||||
opt.setName('query')
|
||||
.setDescription('Song name, URL, or playlist link')
|
||||
.setRequired(true)))
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('skip')
|
||||
.setDescription('Skip the current track'))
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('stop')
|
||||
.setDescription('Stop playback and clear the queue'))
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('pause')
|
||||
.setDescription('Pause the current track'))
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('resume')
|
||||
.setDescription('Resume playback'))
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('queue')
|
||||
.setDescription('View the current queue'))
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('nowplaying')
|
||||
.setDescription('Show the currently playing track'))
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('volume')
|
||||
.setDescription('Adjust the volume')
|
||||
.addIntegerOption(opt =>
|
||||
opt.setName('level')
|
||||
.setDescription('Volume level (0-100)')
|
||||
.setRequired(true)
|
||||
.setMinValue(0)
|
||||
.setMaxValue(100)))
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('shuffle')
|
||||
.setDescription('Shuffle the queue'))
|
||||
.addSubcommand(sub =>
|
||||
sub.setName('loop')
|
||||
.setDescription('Set loop mode')
|
||||
.addStringOption(opt =>
|
||||
opt.setName('mode')
|
||||
.setDescription('Loop mode')
|
||||
.setRequired(true)
|
||||
.addChoices(
|
||||
{ name: 'Off', value: 'off' },
|
||||
{ name: 'Track', value: 'track' },
|
||||
{ name: 'Queue', value: 'queue' }
|
||||
))),
|
||||
|
||||
async execute(interaction, client, supabase) {
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
const player = useMainPlayer();
|
||||
|
||||
const memberVoice = interaction.member.voice.channel;
|
||||
const botVoice = interaction.guild.members.me.voice.channel;
|
||||
|
||||
if (subcommand === 'play') {
|
||||
if (!memberVoice) {
|
||||
return interaction.reply({
|
||||
embeds: [new EmbedBuilder()
|
||||
.setColor(0xff0000)
|
||||
.setDescription('You need to be in a voice channel to play music.')],
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
|
||||
if (botVoice && botVoice.id !== memberVoice.id) {
|
||||
return interaction.reply({
|
||||
embeds: [new EmbedBuilder()
|
||||
.setColor(0xff0000)
|
||||
.setDescription(`I'm already playing music in <#${botVoice.id}>. Join that channel or wait until I'm done.`)],
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
|
||||
const query = interaction.options.getString('query');
|
||||
await interaction.deferReply();
|
||||
|
||||
try {
|
||||
const result = await player.search(query, {
|
||||
requestedBy: interaction.user
|
||||
});
|
||||
|
||||
if (!result.hasTracks()) {
|
||||
return interaction.editReply({
|
||||
embeds: [new EmbedBuilder()
|
||||
.setColor(0xff0000)
|
||||
.setDescription(`No results found for: **${query}**`)]
|
||||
});
|
||||
}
|
||||
|
||||
const { track, searchResult } = await player.play(memberVoice, result, {
|
||||
nodeOptions: {
|
||||
metadata: {
|
||||
channel: interaction.channel,
|
||||
client: interaction.guild.members.me,
|
||||
requestedBy: interaction.user
|
||||
},
|
||||
volume: 50,
|
||||
leaveOnEmpty: true,
|
||||
leaveOnEmptyCooldown: 300000,
|
||||
leaveOnEnd: true,
|
||||
leaveOnEndCooldown: 300000
|
||||
}
|
||||
});
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0x5865f2)
|
||||
.setAuthor({ name: 'Added to Queue', iconURL: interaction.user.displayAvatarURL() });
|
||||
|
||||
if (searchResult.playlist) {
|
||||
embed.setTitle(searchResult.playlist.title)
|
||||
.setURL(searchResult.playlist.url)
|
||||
.setDescription(`Added **${searchResult.tracks.length}** tracks from playlist`)
|
||||
.setThumbnail(searchResult.playlist.thumbnail || track.thumbnail);
|
||||
} else {
|
||||
embed.setTitle(track.title)
|
||||
.setURL(track.url)
|
||||
.setDescription(`**Duration:** ${track.duration}\n**Author:** ${track.author}`)
|
||||
.setThumbnail(track.thumbnail);
|
||||
}
|
||||
|
||||
return interaction.editReply({ embeds: [embed] });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Music play error:', error);
|
||||
|
||||
let errorMessage = 'Something went wrong while trying to play music.';
|
||||
if (error.message?.includes('Sign in')) {
|
||||
errorMessage = 'This track requires age verification or is restricted.';
|
||||
} else if (error.message?.includes('private')) {
|
||||
errorMessage = 'This playlist or video is private.';
|
||||
} else if (error.message?.includes('unavailable')) {
|
||||
errorMessage = 'This content is unavailable in the current region.';
|
||||
} else if (error.message?.includes('Could not extract')) {
|
||||
errorMessage = 'Could not load this track. Try a different search term.';
|
||||
}
|
||||
|
||||
return interaction.editReply({
|
||||
embeds: [new EmbedBuilder()
|
||||
.setColor(0xff0000)
|
||||
.setDescription(errorMessage)]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const queue = useQueue(interaction.guildId);
|
||||
|
||||
if (subcommand === 'nowplaying') {
|
||||
if (!queue || !queue.isPlaying()) {
|
||||
return interaction.reply({
|
||||
embeds: [new EmbedBuilder()
|
||||
.setColor(0xff0000)
|
||||
.setDescription('Nothing is currently playing.')],
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
|
||||
const track = queue.currentTrack;
|
||||
const progress = queue.node.createProgressBar();
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0x5865f2)
|
||||
.setTitle('Now Playing')
|
||||
.setDescription(`**[${track.title}](${track.url})**\n\n${progress}`)
|
||||
.setThumbnail(track.thumbnail)
|
||||
.addFields(
|
||||
{ name: 'Author', value: track.author, inline: true },
|
||||
{ name: 'Duration', value: track.duration, inline: true },
|
||||
{ name: 'Requested By', value: track.requestedBy?.username || 'Unknown', inline: true }
|
||||
)
|
||||
.setFooter({ text: `Volume: ${queue.node.volume}%` });
|
||||
|
||||
return interaction.reply({ embeds: [embed] });
|
||||
}
|
||||
|
||||
if (subcommand === 'queue') {
|
||||
if (!queue || !queue.isPlaying()) {
|
||||
return interaction.reply({
|
||||
embeds: [new EmbedBuilder()
|
||||
.setColor(0xff0000)
|
||||
.setDescription('The queue is empty.')],
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
|
||||
const current = queue.currentTrack;
|
||||
const tracks = queue.tracks.toArray().slice(0, 10);
|
||||
|
||||
let description = `**Now Playing:**\n[${current.title}](${current.url}) - \`${current.duration}\`\n\n`;
|
||||
|
||||
if (tracks.length > 0) {
|
||||
description += '**Up Next:**\n';
|
||||
description += tracks.map((track, i) =>
|
||||
`**${i + 1}.** [${track.title}](${track.url}) - \`${track.duration}\``
|
||||
).join('\n');
|
||||
|
||||
if (queue.tracks.size > 10) {
|
||||
description += `\n\n*...and ${queue.tracks.size - 10} more tracks*`;
|
||||
}
|
||||
} else {
|
||||
description += '*No more tracks in queue*';
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0x5865f2)
|
||||
.setTitle(`Queue for ${interaction.guild.name}`)
|
||||
.setDescription(description)
|
||||
.setFooter({ text: `Total: ${queue.tracks.size + 1} tracks | Volume: ${queue.node.volume}%` });
|
||||
|
||||
return interaction.reply({ embeds: [embed] });
|
||||
}
|
||||
|
||||
if (!queue || !queue.isPlaying()) {
|
||||
return interaction.reply({
|
||||
embeds: [new EmbedBuilder()
|
||||
.setColor(0xff0000)
|
||||
.setDescription('No music is currently playing.')],
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
|
||||
if (memberVoice?.id !== botVoice?.id) {
|
||||
return interaction.reply({
|
||||
embeds: [new EmbedBuilder()
|
||||
.setColor(0xff0000)
|
||||
.setDescription('You need to be in the same voice channel as the bot.')],
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
|
||||
switch (subcommand) {
|
||||
case 'skip': {
|
||||
const current = queue.currentTrack;
|
||||
queue.node.skip();
|
||||
return interaction.reply({
|
||||
embeds: [new EmbedBuilder()
|
||||
.setColor(0x5865f2)
|
||||
.setDescription(`Skipped **${current.title}**`)]
|
||||
});
|
||||
}
|
||||
|
||||
case 'stop': {
|
||||
queue.delete();
|
||||
return interaction.reply({
|
||||
embeds: [new EmbedBuilder()
|
||||
.setColor(0x5865f2)
|
||||
.setDescription('Stopped playback and cleared the queue.')]
|
||||
});
|
||||
}
|
||||
|
||||
case 'pause': {
|
||||
if (queue.node.isPaused()) {
|
||||
return interaction.reply({
|
||||
embeds: [new EmbedBuilder()
|
||||
.setColor(0xff0000)
|
||||
.setDescription('The player is already paused.')],
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
queue.node.pause();
|
||||
return interaction.reply({
|
||||
embeds: [new EmbedBuilder()
|
||||
.setColor(0x5865f2)
|
||||
.setDescription('Paused the player.')]
|
||||
});
|
||||
}
|
||||
|
||||
case 'resume': {
|
||||
if (!queue.node.isPaused()) {
|
||||
return interaction.reply({
|
||||
embeds: [new EmbedBuilder()
|
||||
.setColor(0xff0000)
|
||||
.setDescription('The player is not paused.')],
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
queue.node.resume();
|
||||
return interaction.reply({
|
||||
embeds: [new EmbedBuilder()
|
||||
.setColor(0x5865f2)
|
||||
.setDescription('Resumed playback.')]
|
||||
});
|
||||
}
|
||||
|
||||
case 'volume': {
|
||||
const level = interaction.options.getInteger('level');
|
||||
queue.node.setVolume(level);
|
||||
return interaction.reply({
|
||||
embeds: [new EmbedBuilder()
|
||||
.setColor(0x5865f2)
|
||||
.setDescription(`Volume set to **${level}%**`)]
|
||||
});
|
||||
}
|
||||
|
||||
case 'shuffle': {
|
||||
queue.tracks.shuffle();
|
||||
return interaction.reply({
|
||||
embeds: [new EmbedBuilder()
|
||||
.setColor(0x5865f2)
|
||||
.setDescription('Shuffled the queue.')]
|
||||
});
|
||||
}
|
||||
|
||||
case 'loop': {
|
||||
const mode = interaction.options.getString('mode');
|
||||
const { QueueRepeatMode } = require('discord-player');
|
||||
|
||||
const modes = {
|
||||
off: QueueRepeatMode.OFF,
|
||||
track: QueueRepeatMode.TRACK,
|
||||
queue: QueueRepeatMode.QUEUE
|
||||
};
|
||||
|
||||
queue.setRepeatMode(modes[mode]);
|
||||
return interaction.reply({
|
||||
embeds: [new EmbedBuilder()
|
||||
.setColor(0x5865f2)
|
||||
.setDescription(`Loop mode set to **${mode}**`)]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
2245
aethex-bot/package-lock.json
generated
2245
aethex-bot/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -21,9 +21,11 @@
|
|||
"author": "AeThex Team",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@discord-player/extractor": "^7.1.0",
|
||||
"@discord/embedded-app-sdk": "^2.4.0",
|
||||
"@supabase/supabase-js": "^2.38.0",
|
||||
"axios": "^1.6.0",
|
||||
"discord-player": "^7.1.0",
|
||||
"discord.js": "^14.13.0",
|
||||
"dotenv": "^16.3.1"
|
||||
},
|
||||
|
|
|
|||
2406
package-lock.json
generated
2406
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -10,11 +10,15 @@
|
|||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@discord-player/extractor": "^7.1.0",
|
||||
"@discordjs/voice": "^0.18.0",
|
||||
"canvas": "^3.2.0",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"discord-player": "^7.1.0",
|
||||
"express": "^5.2.1",
|
||||
"express-session": "^1.18.2",
|
||||
"ffmpeg-static": "^5.3.0",
|
||||
"pg": "^8.16.3",
|
||||
"stripe": "^20.0.0",
|
||||
"ws": "^8.18.3"
|
||||
|
|
|
|||
Loading…
Reference in a new issue