38530-vm/index.js
2026-02-17 18:42:06 +00:00

184 lines
6.9 KiB
JavaScript

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);