const { Client, GatewayIntentBits, EmbedBuilder, PermissionsBitField, REST, Routes, SlashCommandBuilder } = require('discord.js'); const { DisTube } = require('distube'); const { YtDlpPlugin } = require('@distube/yt-dlp'); const { YouTubePlugin } = require('@distube/youtube'); const express = require('express'); const ffmpeg = require('ffmpeg-static'); require('dotenv').config(); console.log('FFmpeg Path: ', ffmpeg); // 1. Keep-Alive System const app = express(); app.get('/', (req, res) => res.send('Bot Musik Online!')); app.listen(process.env.PORT || 8080, () => console.log('Keep-Alive aktif di port ' + (process.env.PORT || 8080))); // 2. Discord Client Setup const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent ] }); // 3. DisTube Setup const distube = new DisTube(client, { ffmpegPath: ffmpeg, emitNewSongOnly: true, emitAddSongWhenCreatingQueue: false, emitAddListWhenCreatingQueue: false, plugins: [ new YouTubePlugin(), new YtDlpPlugin() ] }); // 4. Slash Commands Definition const commands = [ new SlashCommandBuilder() .setName('play') .setDescription('Putar musik dari judul atau link') .addStringOption(option => option.setName('query') .setDescription('Judul lagu atau link (YouTube/Spotify/SoundCloud)') .setRequired(true)), new SlashCommandBuilder() .setName('skip') .setDescription('Lewati lagu saat ini'), new SlashCommandBuilder() .setName('stop') .setDescription('Berhentikan musik dan keluar dari voice channel'), new SlashCommandBuilder() .setName('queue') .setDescription('Lihat daftar antrean lagu'), new SlashCommandBuilder() .setName('pause') .setDescription('Jeda musik'), new SlashCommandBuilder() .setName('resume') .setDescription('Lanjutkan musik'), ].map(command => command.toJSON()); // 5. Interaction Handling client.on('interactionCreate', async interaction => { if (!interaction.isChatInputCommand()) return; // WAJIB: Defer Reply di baris pertama agar Discord menunggu (antisipasi "application did not respond") await interaction.deferReply(); const { commandName } = interaction; const voiceChannel = interaction.member.voice.channel; if (!voiceChannel) { return interaction.editReply({ content: 'Kamu harus berada di Voice Channel untuk menggunakan perintah ini!', ephemeral: true }); } try { if (commandName === 'play') { const query = interaction.options.getString('query'); await distube.play(voiceChannel, query, { textChannel: interaction.channel, member: interaction.member }); await interaction.editReply(`🔍 Sedang mencari dan memutar: **${query}**`); } else if (commandName === 'skip') { await distube.skip(interaction.guild); await interaction.editReply('â­ī¸ Lagu dilewati!'); } else if (commandName === 'stop') { await distube.stop(interaction.guild); await interaction.editReply('âšī¸ Musik dihentikan dan bot keluar!'); } else if (commandName === 'pause') { distube.pause(interaction.guild); await interaction.editReply('â¸ī¸ Musik dijeda!'); } else if (commandName === 'resume') { distube.resume(interaction.guild); await interaction.editReply('â–ļī¸ Musik dilanjutkan!'); } else if (commandName === 'queue') { const queue = distube.getQueue(interaction.guild); if (!queue) return interaction.editReply('Antrean kosong!'); const q = queue.songs.map((song, i) => `${i === 0 ? 'Memutar:' : `${i}.`} ${song.name} - \`${song.formattedDuration}\``).join('\n'); await interaction.editReply(`đŸŽļ **Antrean Saat Ini:**\n${q.slice(0, 2000)}`); } } catch (error) { console.error(error); await interaction.editReply(`❌ Terjadi kesalahan: ${error.message}`); } }); // 6. DisTube Events distube .on('playSong', (queue, song) => { queue.textChannel.send(`đŸŽļ Sedang memutar: **${song.name}** - \`${song.formattedDuration}\`\nDiminta oleh: ${song.user}`); }) .on('addSong', (queue, song) => { queue.textChannel.send(`✅ Menambahkan **${song.name}** ke antrean!`); }) .on('error', (channel, e) => { if (channel) channel.send(`❌ Error: ${e.message.slice(0, 1900)}`); console.error(e); }); // 7. Client Ready & Command Registration client.once('ready', async () => { console.log(`Bot logged in as ${client.user.tag}`); // Register Global Slash Commands const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN); try { console.log('Memulai refresh slash commands...'); await rest.put( Routes.applicationCommands(client.user.id), { body: commands }, ); console.log('Berhasil mendaftarkan slash commands!'); } catch (error) { console.error(error); } }); client.login(process.env.DISCORD_TOKEN);