const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 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, supabase, client) { const subcommand = interaction.options.getSubcommand(); const kazagumo = client.kazagumo; if (!kazagumo) { return interaction.reply({ embeds: [new EmbedBuilder() .setColor(0xff0000) .setDescription('Music system is not available. Please try again later.')], ephemeral: true }); } 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 { let player = kazagumo.players.get(interaction.guildId); if (!player) { player = await kazagumo.createPlayer({ guildId: interaction.guildId, textId: interaction.channelId, voiceId: memberVoice.id, shardId: interaction.guild.shardId || 0, volume: 50, deaf: true }); } const result = await kazagumo.search(query, { requester: interaction.user }); if (!result.tracks.length) { return interaction.editReply({ embeds: [new EmbedBuilder() .setColor(0xff0000) .setDescription(`No results found for: **${query}**`)] }); } const embed = new EmbedBuilder() .setColor(0x5865f2) .setAuthor({ name: 'Added to Queue', iconURL: interaction.user.displayAvatarURL() }); if (result.type === 'PLAYLIST') { for (const track of result.tracks) { player.queue.add(track); } embed.setTitle(result.playlistName || 'Playlist') .setDescription(`Added **${result.tracks.length}** tracks from playlist`) .setThumbnail(result.tracks[0]?.thumbnail || null); } else { const track = result.tracks[0]; player.queue.add(track); embed.setTitle(track.title) .setURL(track.uri) .setDescription(`**Duration:** ${formatDuration(track.length)}\n**Author:** ${track.author || 'Unknown'}`) .setThumbnail(track.thumbnail || null); } if (!player.playing && !player.paused) { player.play(); } 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('No node')) { errorMessage = 'Music servers are currently offline. Please try again later.'; } return interaction.editReply({ embeds: [new EmbedBuilder() .setColor(0xff0000) .setDescription(errorMessage)] }); } } const player = kazagumo.players.get(interaction.guildId); if (subcommand === 'nowplaying') { if (!player || !player.playing) { return interaction.reply({ embeds: [new EmbedBuilder() .setColor(0xff0000) .setDescription('Nothing is currently playing.')], ephemeral: true }); } const track = player.queue.current; const position = player.shoukaku.position || 0; const duration = track.length || 0; const progress = createProgressBar(position, duration); const embed = new EmbedBuilder() .setColor(0x5865f2) .setTitle('Now Playing') .setDescription(`**[${track.title}](${track.uri})**\n\n${progress}`) .setThumbnail(track.thumbnail || null) .addFields( { name: 'Author', value: track.author || 'Unknown', inline: true }, { name: 'Duration', value: formatDuration(duration), inline: true }, { name: 'Requested By', value: track.requester?.username || 'Unknown', inline: true } ) .setFooter({ text: `Volume: ${player.volume}%` }); return interaction.reply({ embeds: [embed] }); } if (subcommand === 'queue') { if (!player || !player.queue.current) { return interaction.reply({ embeds: [new EmbedBuilder() .setColor(0xff0000) .setDescription('The queue is empty.')], ephemeral: true }); } const current = player.queue.current; const tracks = player.queue.slice(0, 10); let description = `**Now Playing:**\n[${current.title}](${current.uri}) - \`${formatDuration(current.length)}\`\n\n`; if (tracks.length > 0) { description += '**Up Next:**\n'; description += tracks.map((track, i) => `**${i + 1}.** [${track.title}](${track.uri}) - \`${formatDuration(track.length)}\`` ).join('\n'); if (player.queue.size > 10) { description += `\n\n*...and ${player.queue.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: ${player.queue.size + 1} tracks | Volume: ${player.volume}%` }); return interaction.reply({ embeds: [embed] }); } if (!player || !player.playing) { 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 = player.queue.current; player.skip(); return interaction.reply({ embeds: [new EmbedBuilder() .setColor(0x5865f2) .setDescription(`Skipped **${current?.title || 'current track'}**`)] }); } case 'stop': { player.destroy(); return interaction.reply({ embeds: [new EmbedBuilder() .setColor(0x5865f2) .setDescription('Stopped playback and cleared the queue.')] }); } case 'pause': { if (player.paused) { return interaction.reply({ embeds: [new EmbedBuilder() .setColor(0xff0000) .setDescription('The player is already paused.')], ephemeral: true }); } player.pause(true); return interaction.reply({ embeds: [new EmbedBuilder() .setColor(0x5865f2) .setDescription('Paused the player.')] }); } case 'resume': { if (!player.paused) { return interaction.reply({ embeds: [new EmbedBuilder() .setColor(0xff0000) .setDescription('The player is not paused.')], ephemeral: true }); } player.pause(false); return interaction.reply({ embeds: [new EmbedBuilder() .setColor(0x5865f2) .setDescription('Resumed playback.')] }); } case 'volume': { const level = interaction.options.getInteger('level'); player.setVolume(level); return interaction.reply({ embeds: [new EmbedBuilder() .setColor(0x5865f2) .setDescription(`Volume set to **${level}%**`)] }); } case 'shuffle': { player.queue.shuffle(); return interaction.reply({ embeds: [new EmbedBuilder() .setColor(0x5865f2) .setDescription('Shuffled the queue.')] }); } case 'loop': { const mode = interaction.options.getString('mode'); const loopModes = { off: 'none', track: 'track', queue: 'queue' }; player.setLoop(loopModes[mode]); return interaction.reply({ embeds: [new EmbedBuilder() .setColor(0x5865f2) .setDescription(`Loop mode set to **${mode}**`)] }); } } } }; function formatDuration(ms) { if (!ms) return '0:00'; const seconds = Math.floor((ms / 1000) % 60); const minutes = Math.floor((ms / (1000 * 60)) % 60); const hours = Math.floor(ms / (1000 * 60 * 60)); if (hours > 0) { return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; } return `${minutes}:${seconds.toString().padStart(2, '0')}`; } function createProgressBar(current, total, length = 15) { if (!total) return '▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬'; const percentage = current / total; const progress = Math.round(length * percentage); const empty = length - progress; const progressBar = '▓'.repeat(progress) + '▒'.repeat(empty); return `\`${formatDuration(current)}\` ${progressBar} \`${formatDuration(total)}\``; }