process.env.FFMPEG_PATH = require('ffmpeg-static'); const path = require('path'); // Tambahkan folder ffmpeg ke PATH agar yt-dlp bisa menemukannya const ffmpegDir = path.dirname(process.env.FFMPEG_PATH); if (!process.env.PATH.includes(ffmpegDir)) { process.env.PATH = `${ffmpegDir}${path.delimiter}${process.env.PATH}`; } require('dotenv').config({ path: path.join(__dirname, '.env') }); const { Client, GatewayIntentBits, Events, EmbedBuilder } = require('discord.js'); const { DisTube } = require('distube'); const { YtDlpPlugin } = require('@distube/yt-dlp'); const { SpotifyPlugin } = require('@distube/spotify'); const { SoundCloudPlugin } = require('@distube/soundcloud'); const { CronJob } = require('cron'); const fs = require('fs'); const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent ] }); // Initialize DisTube with the requested plugins const distube = new DisTube(client, { plugins: [ new SpotifyPlugin(), new SoundCloudPlugin(), new YtDlpPlugin() // yt-dlp must be last ], emitNewSongOnly: true, emitAddSongWhenCreatingQueue: false, emitAddListWhenCreatingQueue: false }); const VC_ID = process.env.VC_ID; const SAHUR_AUDIO_PATH = path.join(__dirname, 'assets/audio/sahur.mp3'); function logToFile(message) { const logMessage = `[${new Date().toISOString()}] ${message}\n`; fs.appendFileSync(path.join(__dirname, 'bot.log'), logMessage); console.log(message); } // Better Error Handling for DisTube distube .on('playSong', (queue, song) => { logToFile(`Playing: ${song.name}`); const embed = new EmbedBuilder() .setColor('#00FF00') .setTitle('🎢 Sedang Memutar') .setDescription(`**${song.name}**`) .addFields( { name: 'Durasi', value: song.formattedDuration, inline: true }, { name: 'Platform', value: song.source.toUpperCase(), inline: true } ) .setThumbnail(song.thumbnail); queue.textChannel.send({ embeds: [embed] }); }) .on('addSong', (queue, song) => { queue.textChannel.send(`βœ… **${song.name}** ditambahkan ke antrean!`); }) .on('error', (channel, e) => { const errorMsg = `DisTube Error: ${e.stack || e.message || e}`; logToFile(errorMsg); if (channel && channel.send) channel.send(`❌ Terjadi kesalahan audio: ${e.message.slice(0, 1000)}`); }) .on('empty', queue => { if (queue.textChannel) queue.textChannel.send('Voice channel kosong, bot keluar...'); }) .on('searchNoResult', (message, query) => { if (message.channel) message.channel.send(`❌ Tidak ada hasil untuk: ${query}`); }); client.once(Events.ClientReady, async c => { logToFile(`Ready! Logged in as ${c.user.tag}`); logToFile(`FFMPEG Path: ${process.env.FFMPEG_PATH}`); // Sahur Alarm const sahurTime = process.env.SAHUR_TIME || '30 03 * * *'; let cronTime = sahurTime; if (sahurTime.includes(':') && !sahurTime.includes('*')) { const [hour, minute] = sahurTime.split(':'); cronTime = `0 ${minute} ${hour} * * *`; } try { new CronJob(cronTime, async () => { logToFile('Sahur alarm triggered!'); const guild = client.guilds.cache.first(); if (!guild) return; const channel = guild.channels.cache.get(VC_ID); if (channel) { distube.play(channel, SAHUR_AUDIO_PATH, { skip: true, textChannel: guild.systemChannel || guild.channels.cache.find(ch => ch.type === 0) }); } }, null, true, 'Asia/Jakarta'); logToFile(`Alarm scheduled at ${cronTime}`); } catch (e) { logToFile(`Failed to schedule alarm: ${e.message}`); } }); client.on(Events.InteractionCreate, async interaction => { if (!interaction.isChatInputCommand()) return; const { commandName } = interaction; const voiceChannel = interaction.member?.voice.channel; if (['play', 'testsahur', 'skip', 'stop', 'pause', 'resume'].includes(commandName) && !voiceChannel) { return interaction.reply({ content: 'Kamu harus berada di voice channel!', ephemeral: true }); } try { if (commandName === 'testsahur') { await interaction.deferReply(); logToFile('Executing /testsahur...'); if (!fs.existsSync(SAHUR_AUDIO_PATH)) { throw new Error(`File tidak ditemukan di: ${SAHUR_AUDIO_PATH}`); } await distube.play(voiceChannel, SAHUR_AUDIO_PATH, { skip: true, member: interaction.member, textChannel: interaction.channel }); await interaction.editReply('πŸ“’ Memutar suara Sahur untuk pengetesan!'); } else if (commandName === 'play') { const query = interaction.options.getString('query'); await interaction.deferReply(); logToFile(`Executing /play with query: ${query}`); if (query.toLowerCase() === 'sahur') { await distube.play(voiceChannel, SAHUR_AUDIO_PATH, { skip: true, member: interaction.member, textChannel: interaction.channel }); return interaction.editReply('πŸ“’ Memainkan audio Sahur lokal...'); } await distube.play(voiceChannel, query, { textChannel: interaction.channel, member: interaction.member }); await interaction.editReply(`πŸ” Mencari: **${query}**`); } else if (commandName === 'pause') { await interaction.deferReply(); distube.pause(interaction.guildId); await interaction.editReply('Musik dipause. ⏸️'); } else if (commandName === 'resume') { await interaction.deferReply(); distube.resume(interaction.guildId); await interaction.editReply('Musik dilanjutkan. ▢️'); } else if (commandName === 'skip') { await interaction.deferReply(); await distube.skip(interaction.guildId); await interaction.editReply('Lagu dilewati! ⏭️'); } else if (commandName === 'stop') { await interaction.deferReply(); await distube.stop(interaction.guildId); await interaction.editReply('Musik dihentikan.'); } } catch (err) { logToFile(`Command Error (${commandName}): ${err.stack || err.message}`); if (interaction.deferred || interaction.replied) { await interaction.editReply(`❌ Error: ${err.message}`); } else { await interaction.reply({ content: `❌ Error: ${err.message}`, ephemeral: true }); } } }); client.login(process.env.DISCORD_TOKEN);