diff --git a/bot/bot.log b/bot/bot.log index 9a47c21..7d8be63 100644 --- a/bot/bot.log +++ b/bot/bot.log @@ -1263,3 +1263,8 @@ Alarm scheduled at 0 30 03 * * * [2026-02-16T06:49:05.559Z] Ready! Logged in as AsepXiaoQin#6954 [2026-02-16T06:49:05.563Z] FFMPEG Path: /home/ubuntu/executor/workspace/bot/node_modules/ffmpeg-static/ffmpeg [2026-02-16T06:49:06.586Z] Alarm scheduled at 0 30 03 * * * +[2026-02-16T07:06:43.653Z] Ready! Logged in as AsepXiaoQin#6954 +[2026-02-16T07:06:43.654Z] FFMPEG Path: /home/ubuntu/executor/workspace/bot/node_modules/ffmpeg-static/ffmpeg +[2026-02-16T07:06:43.786Z] Alarm scheduled at 0 30 03 * * * +[2026-02-16T07:12:30.682Z] Ready! Logged in as AsepXiaoQin#6954 +[2026-02-16T07:12:30.922Z] Alarm scheduled at 0 30 03 * * * diff --git a/bot/index.js b/bot/index.js index c82751b..fef7606 100644 --- a/bot/index.js +++ b/bot/index.js @@ -1,20 +1,20 @@ process.env.FFMPEG_PATH = require('ffmpeg-static'); const path = require('path'); -// Tambahkan folder ffmpeg ke PATH agar yt-dlp bisa menemukannya +const fs = require('fs'); +const { Client, GatewayIntentBits, Events } = require('discord.js'); +const { joinVoiceChannel, createAudioPlayer, createAudioResource, AudioPlayerStatus, NoSubscriberBehavior } = require('@discordjs/voice'); +const { CronJob } = require('cron'); +const play = require('play-dl'); + +// Ensure .env is loaded early +require('dotenv').config({ path: path.join(__dirname, '.env') }); + +// Tambahkan folder ffmpeg ke PATH 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, @@ -24,61 +24,27 @@ const client = new Client({ ] }); -// 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'); +const AUDIO_PATH = path.join(__dirname, 'assets/audio/sahur.mp3'); +const PREFIX = '!'; + +// Global state +const queues = new Map(); // guildId -> { queue: [], player, connection } function logToFile(message) { const logMessage = `[${new Date().toISOString()}] ${message}\n`; - fs.appendFileSync(path.join(__dirname, 'bot.log'), logMessage); + try { + fs.appendFileSync(path.join(__dirname, 'bot.log'), logMessage); + } catch (err) { + console.error('Failed to write to log file:', err); + } 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}`); - +client.once(Events.ClientReady, () => { + console.log('Bot berhasil login!'); + logToFile(`Ready! Logged in as ${client.user.tag}`); + // Sahur Alarm const sahurTime = process.env.SAHUR_TIME || '30 03 * * *'; let cronTime = sahurTime; @@ -94,10 +60,7 @@ client.once(Events.ClientReady, async c => { 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) - }); + playLocal(channel, AUDIO_PATH); } }, null, true, 'Asia/Jakarta'); logToFile(`Alarm scheduled at ${cronTime}`); @@ -106,86 +69,163 @@ client.once(Events.ClientReady, async c => { } }); -client.on(Events.InteractionCreate, async interaction => { - if (!interaction.isChatInputCommand()) return; - - const { commandName } = interaction; - const voiceChannel = interaction.member?.voice.channel; +async function playLocal(channel, filePath) { + try { + const connection = joinVoiceChannel({ + channelId: channel.id, + guildId: channel.guild.id, + adapterCreator: channel.guild.voiceAdapterCreator, + }); - if (['play', 'testsahur', 'skip', 'stop', 'pause', 'resume'].includes(commandName) && !voiceChannel) { - return interaction.reply({ content: 'Kamu harus berada di voice channel!', ephemeral: true }); + const player = createAudioPlayer(); + const resource = createAudioResource(filePath); + player.play(resource); + connection.subscribe(player); + + player.once(AudioPlayerStatus.Idle, () => { + connection.destroy(); + }); + } catch (err) { + logToFile(`Error playing local file: ${err.message}`); } +} + +client.on(Events.MessageCreate, async (message) => { + if (message.author.bot || !message.content.startsWith(PREFIX)) return; + + const args = message.content.slice(PREFIX.length).trim().split(/ +/); + const command = args.shift().toLowerCase(); + const voiceChannel = message.member?.voice.channel; try { - if (commandName === 'testsahur') { - await interaction.deferReply(); - logToFile('Executing /testsahur...'); + if (command === 'join') { + if (!voiceChannel) return message.reply('Anda harus berada di voice channel!'); + joinVoiceChannel({ + channelId: voiceChannel.id, + guildId: message.guild.id, + adapterCreator: message.guild.voiceAdapterCreator, + }); + message.reply('Sudah join! 🎧'); + } + + else if (command === 'testsahur') { + if (!voiceChannel) return message.reply('Anda harus berada di voice channel!'); + if (!fs.existsSync(AUDIO_PATH)) return message.reply(`File audio tidak ditemukan di ${AUDIO_PATH}`); + playLocal(voiceChannel, AUDIO_PATH); + message.reply('Memainkan suara sahur... πŸ“’'); + } + + else if (command === 'play') { + if (!voiceChannel) return message.reply('Anda harus berada di voice channel!'); + const query = args.join(' '); + if (!query) return message.reply('Berikan link atau nama lagu!'); + + let serverQueue = queues.get(message.guild.id); + + if (!serverQueue) { + serverQueue = { + songs: [], + connection: null, + player: createAudioPlayer({ + behaviors: { noSubscriber: NoSubscriberBehavior.Play } + }), + }; + queues.set(message.guild.id, serverQueue); + } + + message.reply('Mencari lagu... πŸ”'); - if (!fs.existsSync(SAHUR_AUDIO_PATH)) { - throw new Error(`File tidak ditemukan di: ${SAHUR_AUDIO_PATH}`); + let songInfo; + if (play.sp_validate(query) === 'track') { + const sp_data = await play.spotify(query); + const search = await play.search(`${sp_data.name} ${sp_data.artists[0].name}`, { limit: 1 }); + songInfo = { title: sp_data.name, url: search[0].url }; + } else if (play.so_validate(query)) { + const so_data = await play.soundcloud(query); + songInfo = { title: so_data.name, url: so_data.url }; + } else { + const yt_info = await play.search(query, { limit: 1 }); + if (yt_info.length === 0) return message.channel.send('Lagu tidak ditemukan!'); + songInfo = { title: yt_info[0].title, url: yt_info[0].url }; } - await distube.play(voiceChannel, SAHUR_AUDIO_PATH, { - skip: true, - member: interaction.member, - textChannel: interaction.channel - }); - await interaction.editReply('πŸ“’ Memutar suara Sahur untuk pengetesan!'); - } + serverQueue.songs.push(songInfo); - 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...'); + if (serverQueue.songs.length === 1) { + playSong(message.guild.id, voiceChannel); + message.channel.send(`🎡 Sekarang memutar: **${songInfo.title}**`); + } else { + message.channel.send(`βœ… **${songInfo.title}** ditambahkan ke antrean.`); } - - 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 (command === 'skip') { + const serverQueue = queues.get(message.guild.id); + if (!serverQueue) return message.reply('Tidak ada lagu yang sedang diputar!'); + serverQueue.player.stop(); + message.reply('Lagu dilewati! ⏭️'); } - else if (commandName === 'resume') { - await interaction.deferReply(); - distube.resume(interaction.guildId); - await interaction.editReply('Musik dilanjutkan. ▢️'); + else if (command === 'stop') { + const serverQueue = queues.get(message.guild.id); + if (serverQueue) { + serverQueue.songs = []; + serverQueue.player.stop(); + if (serverQueue.connection) serverQueue.connection.destroy(); + queues.delete(message.guild.id); + } + message.reply('Musik dihentikan. πŸ‘‹'); } - else if (commandName === 'skip') { - await interaction.deferReply(); - await distube.skip(interaction.guildId); - await interaction.editReply('Lagu dilewati! ⏭️'); + else if (command === 'pause') { + const serverQueue = queues.get(message.guild.id); + if (serverQueue) serverQueue.player.pause(); + message.reply('Dipause. ⏸️'); } - 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 }); + else if (command === 'resume') { + const serverQueue = queues.get(message.guild.id); + if (serverQueue) serverQueue.player.unpause(); + message.reply('Dilanjutkan. ▢️'); } + } catch (error) { + logToFile(`Command Error (${command}): ${error.stack || error.message}`); + message.reply(`❌ Terjadi kesalahan: ${error.message}`); } }); +async function playSong(guildId, channel) { + const serverQueue = queues.get(guildId); + if (!serverQueue || serverQueue.songs.length === 0) { + return; + } + + try { + const song = serverQueue.songs[0]; + const stream = await play.stream(song.url); + const resource = createAudioResource(stream.stream, { inputType: stream.type }); + + if (!serverQueue.connection) { + serverQueue.connection = joinVoiceChannel({ + channelId: channel.id, + guildId: guildId, + adapterCreator: channel.guild.voiceAdapterCreator, + }); + serverQueue.connection.subscribe(serverQueue.player); + } + + serverQueue.player.play(resource); + + serverQueue.player.once(AudioPlayerStatus.Idle, () => { + serverQueue.songs.shift(); + playSong(guildId, channel); + }); + } catch (err) { + logToFile(`Playback Error: ${err.message}`); + serverQueue.songs.shift(); + playSong(guildId, channel); + } +} + +// Login at the very bottom client.login(process.env.DISCORD_TOKEN);