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; const { commandName } = interaction; if (commandName === 'play') { // PRIORITAS UTAMA: Defer secepat mungkin untuk menghindari timeout await interaction.deferReply(); console.log('Command diterima, sedang memproses...'); try { const query = interaction.options.getString('query'); if (!interaction.member.voice.channel) { return interaction.editReply('Kamu harus berada di voice channel untuk menggunakan bot ini!'); } // Memberikan feedback awal await interaction.editReply({ content: `πŸ” Sedang mencari lagu: **${query}**...` }); await distube.play(interaction.member.voice.channel, query, { textChannel: interaction.channel, member: interaction.member, interaction }); } catch (error) { console.error('Play Error:', error); await interaction.editReply({ content: `❌ Terjadi kesalahan: ${error.message}` }); } } else { // Untuk command lain, kita tetap gunakan try-catch global try { await interaction.deferReply(); if (!interaction.member.voice.channel) { return interaction.editReply('Kamu harus berada di voice channel!'); } 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!'); } else if (commandName === 'pause') { const queue = distube.getQueue(interaction.guild); if (!queue) return interaction.editReply('❌ Tidak ada antrean!'); distube.pause(interaction.guild); await interaction.editReply('⏸️ Musik dijeda!'); } else if (commandName === 'resume') { const queue = distube.getQueue(interaction.guild); if (!queue) return interaction.editReply('❌ Tidak ada antrean!'); 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 ? '▢️' : `${i}.`} ${song.name}`) .join('\n'); await interaction.editReply(`🎢 **Antrean:**\n${q.slice(0, 1900)}`); } } catch (error) { console.error('Interaction Error:', error); if (interaction.deferred || interaction.replied) { await interaction.editReply({ content: `❌ Error: ${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) { 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);