console.log('Bot Starting...'); 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'); require('dotenv').config(); // 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, { 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; // Force Defer: Baris paling pertama setelah pengecekan command await interaction.deferReply({ ephemeral: false }); console.log(`Command /${interaction.commandName} diterima dari: ${interaction.user.tag}`); // 2. Gunakan Try-Catch: Bungkus semua proses agar bot tidak mati jika error try { const { commandName } = interaction; // 3. Sinkronisasi Voice: Pastikan user ada di voice channel if (!interaction.member.voice.channel) { return interaction.editReply('Kamu harus berada di voice channel untuk menggunakan bot ini!'); } if (commandName === 'play') { const query = interaction.options.getString('query'); // Memberikan feedback awal await interaction.editReply({ content: `πŸ” Sedang mencari lagu: **${query}**...` }); // 4. Fix DisTube Play: Pemanggilan profesional dengan parameter lengkap await distube.play(interaction.member.voice.channel, query, { textChannel: interaction.channel, member: interaction.member, interaction // Menyertakan interaksi jika diperlukan oleh plugin }); } else if (commandName === 'skip') { const queue = distube.getQueue(interaction.guild); if (!queue) return interaction.editReply('❌ Tidak ada lagu yang sedang diputar!'); await distube.skip(interaction.guild); await interaction.editReply('⏭️ Lagu berhasil dilewati!'); } else if (commandName === 'stop') { await distube.stop(interaction.guild); await interaction.editReply('⏹️ Musik dihentikan dan bot keluar dari voice channel!'); } else if (commandName === 'pause') { const queue = distube.getQueue(interaction.guild); if (!queue) return interaction.editReply('❌ Tidak ada lagu untuk dijeda!'); distube.pause(interaction.guild); await interaction.editReply('⏸️ Musik berhasil dijeda!'); } else if (commandName === 'resume') { const queue = distube.getQueue(interaction.guild); if (!queue) return interaction.editReply('❌ Tidak ada lagu untuk dilanjutkan!'); 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 saat ini kosong!'); const q = queue.songs .map((song, i) => `${i === 0 ? '▢️ **Memutar:**' : `**${i}.**`} ${song.name} - \`${song.formattedDuration}\``) .join('\n'); await interaction.editReply(`🎢 **Daftar Antrean:**\n${q.slice(0, 2000)}`); } } catch (error) { console.error('Interaction Error:', error); // Cek apakah interaksi sudah di-defer atau di-reply untuk menghindari error tambahan if (interaction.deferred || interaction.replied) { await interaction.editReply({ content: `❌ Terjadi kesalahan: ${error.message}` }); } else { await interaction.reply({ content: `❌ Terjadi kesalahan fatal: ${error.message}`, ephemeral: true }); } } }); // 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) { const errorMessage = e && e.message ? e.message.slice(0, 1900) : "Unknown Error"; channel.send(`❌ Error: ${errorMessage}`).catch(console.error); } console.error('DisTube 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); } }); // 8. Global Error Handling (Anti-Crash) process.on('unhandledRejection', error => { console.error('Unhandled promise rejection:', error); }); process.on('uncaughtException', error => { console.error('Uncaught exception:', error); }); client.login(process.env.DISCORD_TOKEN);