require('dotenv').config(); const { Client, GatewayIntentBits, REST, Routes, SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js'); const { joinVoiceChannel, createAudioPlayer, createAudioResource, AudioPlayerStatus, VoiceConnectionStatus } = require('@discordjs/voice'); const express = require('express'); const multer = require('multer'); const cron = require('node-cron'); const fs = require('fs'); const path = require('path'); // --- Configuration --- const DISCORD_TOKEN = process.env.DISCORD_TOKEN; const CLIENT_ID = process.env.CLIENT_ID; const GUILD_ID = process.env.GUILD_ID; const VOICE_CHANNEL_ID = process.env.VOICE_CHANNEL_ID; const TEXT_CHANNEL_ID = process.env.TEXT_CHANNEL_ID; let sahurHour = parseInt(process.env.SAHUR_HOUR) || 3; let sahurMinute = parseInt(process.env.SAHUR_MINUTE) || 0; const app = express(); const port = 8080; // Multer for MP3 Upload const storage = multer.diskStorage({ destination: (req, file, cb) => cb(null, 'uploads/'), filename: (req, file, cb) => cb(null, 'sahur.mp3') }); const upload = multer({ storage }); // Discord Client const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent ] }); // --- Bot Logic --- async function playSahurAudio(targetVoiceId = VOICE_CHANNEL_ID) { const guild = client.guilds.cache.get(GUILD_ID); if (!guild) return console.error('Guild not found'); const voiceChannel = guild.channels.cache.get(targetVoiceId); if (!voiceChannel) return console.error('Voice channel not found'); const filePath = path.join(__dirname, 'uploads', 'sahur.mp3'); if (!fs.existsSync(filePath)) { const textChannel = guild.channels.cache.get(TEXT_CHANNEL_ID); if (textChannel) textChannel.send('⚠️ Peringatan: file uploads/sahur.mp3 belum ada!'); return; } const connection = joinVoiceChannel({ channelId: voiceChannel.id, guildId: guild.id, adapterCreator: guild.voiceAdapterCreator, }); const player = createAudioPlayer(); const resource = createAudioResource(filePath); player.play(resource); connection.subscribe(player); player.on(AudioPlayerStatus.Idle, () => { connection.destroy(); }); player.on('error', error => { console.error('Error playing audio:', error); connection.destroy(); }); } // Scheduled Job let cronTask; function setupCron() { if (cronTask) cronTask.stop(); const cronExpression = `${sahurMinute} ${sahurHour} * * *`; cronTask = cron.schedule(cronExpression, () => { console.log(`Running scheduled sahur playback at ${sahurHour}:${sahurMinute}`); playSahurAudio(); }); console.log(`Scheduled sahur at ${sahurHour}:${sahurMinute}`); } // Slash Commands const commands = [ new SlashCommandBuilder() .setName('testsahur') .setDescription('Tes bunyi sahur di voice channel sekarang'), new SlashCommandBuilder() .setName('setsahur') .setDescription('Ubah jadwal sahur') .addIntegerOption(option => option.setName('jam').setDescription('Jam (0-23)').setRequired(true)) .addIntegerOption(option => option.setName('menit').setDescription('Menit (0-59)').setRequired(true)), new SlashCommandBuilder() .setName('statussahur') .setDescription('Cek status bot sahur'), ].map(command => command.toJSON()); const rest = new REST({ version: '10' }).setToken(DISCORD_TOKEN); async function registerCommands() { try { console.log('Started refreshing application (/) commands.'); await rest.put(Routes.applicationGuildCommands(CLIENT_ID, GUILD_ID), { body: commands }); console.log('Successfully reloaded application (/) commands.'); } catch (error) { console.error(error); } } client.on('ready', () => { console.log(`Bot logged in as ${client.user.tag}`); registerCommands(); setupCron(); }); client.on('interactionCreate', async interaction => { if (!interaction.isChatInputCommand()) return; if (interaction.commandName === 'testsahur') { const member = interaction.member; const voiceChannel = member.voice.channel; if (!voiceChannel) { return interaction.reply({ content: 'Kamu harus berada di voice channel untuk melakukan tes!', ephemeral: true }); } await interaction.reply('Memulai tes bunyi sahur...'); playSahurAudio(voiceChannel.id); } if (interaction.commandName === 'setsahur') { sahurHour = interaction.options.getInteger('jam'); sahurMinute = interaction.options.getInteger('menit'); setupCron(); await interaction.reply(`Jadwal sahur diubah ke pukul ${sahurHour}:${sahurMinute.toString().padStart(2, '0')}`); } if (interaction.commandName === 'statussahur') { const filePath = path.join(__dirname, 'uploads', 'sahur.mp3'); const fileExists = fs.existsSync(filePath); const stats = fileExists ? fs.statSync(filePath) : null; const fileSize = stats ? (stats.size / (1024 * 1024)).toFixed(2) + ' MB' : 'N/A'; await interaction.reply({ content: `**Status Bot Sahur:**\n` + `- File MP3: ${fileExists ? '✅ Ada' : '❌ Tidak ada'}\n` + `- Ukuran File: ${fileSize}\n` + `- Jadwal Berikutnya: ${sahurHour}:${sahurMinute.toString().padStart(2, '0')}\n` + `- Voice Channel: <#${VOICE_CHANNEL_ID}>` }); } }); // --- Dashboard Logic --- app.use(express.static('public')); app.use(express.urlencoded({ extended: true })); app.get('/', (req, res) => { const filePath = path.join(__dirname, 'uploads', 'sahur.mp3'); const fileExists = fs.existsSync(filePath); const stats = fileExists ? fs.statSync(filePath) : null; const fileSize = stats ? (stats.size / (1024 * 1024)).toFixed(2) + ' MB' : 'N/A'; res.send(`
File sahur.mp3: ${fileExists ? 'Ready (' + fileSize + ')' : 'Missing'}
Jadwal: ${sahurHour}:${sahurMinute.toString().padStart(2, '0')}