38465-vm/bot/index.js
Flatlogic Bot e6bef456ac V12
2026-02-16 10:02:55 +00:00

302 lines
11 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

process.env.FFMPEG_PATH = require('ffmpeg-static');
const ffmpeg = require('ffmpeg-static');
const path = require('path');
if (ffmpeg) {
const ffmpegDir = path.dirname(ffmpeg);
if (!process.env.PATH.includes(ffmpegDir)) {
process.env.PATH = `${ffmpegDir}${path.delimiter}${process.env.PATH}`;
}
}
const { Client, GatewayIntentBits, Events, EmbedBuilder, PermissionsBitField } = require('discord.js');
const { DisTube } = require('distube');
const { SoundCloudPlugin } = require('@distube/soundcloud');
const { SpotifyPlugin } = require('@distube/spotify');
const { YtDlpPlugin } = require('@distube/yt-dlp');
const play = require('play-dl');
const {
joinVoiceChannel,
createAudioPlayer,
createAudioResource,
AudioPlayerStatus,
StreamType
} = require('@discordjs/voice');
const { CronJob } = require('cron');
const fs = require('fs');
// Load environment variables
require('dotenv').config({ path: path.join(__dirname, '.env') });
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildVoiceStates,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildMembers // Added to fetch members for alarm
]
});
// Detect libsodium
try {
const sodium = require('libsodium-wrappers');
sodium.ready.then(() => {
logToFile("Encryption library (libsodium) is ready.");
});
} catch (e) {
logToFile("Warning: libsodium-wrappers not found or failed to load.");
}
const distube = new DisTube(client, {
ffmpegPath: require('ffmpeg-static'),
plugins: [
new SpotifyPlugin(),
new SoundCloudPlugin(),
new YtDlpPlugin({ update: true })
]
});
const VC_ID = process.env.VC_ID;
const AUDIO_PATH = path.join(__dirname, 'assets', 'audio', 'sahur.mp3');
const PREFIX = '!';
function logToFile(message) {
const logMessage = `[${new Date().toISOString()}] ${message}\n`;
try {
fs.appendFileSync(path.join(__dirname, 'bot.log'), logMessage);
} catch (err) {
console.error('Failed to write to log file:', err);
}
console.log(message);
}
async function playLocalIndependent(channel) {
if (!fs.existsSync(AUDIO_PATH)) {
logToFile(`Audio file not found: ${AUDIO_PATH}`);
return;
}
try {
// Leave DisTube first to avoid collision
await distube.voices.leave(channel.guild.id);
const connection = joinVoiceChannel({
channelId: channel.id,
guildId: channel.guild.id,
adapterCreator: channel.guild.voiceAdapterCreator,
selfDeaf: false,
});
const player = createAudioPlayer();
const resource = createAudioResource(AUDIO_PATH, {
inputType: StreamType.Arbitrary
});
player.play(resource);
connection.subscribe(player);
player.on(AudioPlayerStatus.Idle, () => {
connection.destroy();
});
player.on('error', error => {
logToFile(`Audio Player Error: ${error.message}`);
connection.destroy();
});
} catch (err) {
logToFile(`Independent play error: ${err.message}`);
}
}
client.once(Events.ClientReady, () => {
logToFile(`Bot berhasil login!`);
logToFile(`Ready! Logged in as ${client.user.tag}`);
logToFile(`FFMPEG Path: ${process.env.FFMPEG_PATH}`);
// Sahur Alarm
const rawSahurTime = process.env.SAHUR_TIME || '03:30';
let cronTime = '30 3 * * *';
if (rawSahurTime.includes(':')) {
const [hour, minute] = rawSahurTime.split(':');
cronTime = `${parseInt(minute)} ${parseInt(hour)} * * *`;
} else {
cronTime = rawSahurTime;
}
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 && fs.existsSync(AUDIO_PATH)) {
try {
await playLocalIndependent(channel);
logToFile('Alarm playing via independent @discordjs/voice.');
} catch (err) {
logToFile(`Alarm play error: ${err.message}`);
}
}
}, null, true, 'Asia/Jakarta');
logToFile(`Alarm scheduled at ${cronTime}`);
} catch (e) {
logToFile(`Failed to schedule alarm: ${e.message}`);
}
});
// Slash Command Handler
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
const { commandName } = interaction;
const voiceChannel = interaction.member?.voice.channel;
try {
if (commandName === 'join') {
if (!voiceChannel) return interaction.reply({ content: 'Anda harus berada di voice channel!', ephemeral: true });
await interaction.deferReply();
await distube.voices.join(voiceChannel, { selfDeaf: false, selfMute: false });
await interaction.editReply('Sudah join! 🎧');
}
else if (commandName === 'testsahur') {
if (!voiceChannel) return interaction.reply({ content: 'Anda harus berada di voice channel!', ephemeral: true });
if (!fs.existsSync(AUDIO_PATH)) return interaction.reply({ content: `File audio tidak ditemukan di ${AUDIO_PATH}`, ephemeral: true });
await interaction.deferReply();
logToFile(`Playing local file independently: ${AUDIO_PATH}`);
try {
await playLocalIndependent(voiceChannel);
await interaction.editReply('Memainkan suara sahur... 📢');
} catch (e) {
logToFile(`Error in testsahur: ${e.message}`);
await interaction.editReply(`Gagal memutar audio: ${e.message}`);
}
}
else if (commandName === 'play') {
await interaction.deferReply();
const query = interaction.options.getString('query');
const voiceChannel = interaction.member?.voice.channel;
if (!voiceChannel) return interaction.editReply({ content: 'Anda harus berada di voice channel!' });
try {
await distube.play(voiceChannel, query, {
textChannel: interaction.channel,
member: interaction.member
});
await interaction.editReply(`Mencari dan memutar: **${query}**... 🎶`);
} catch (e) {
logToFile(`Play Error: ${e.message}`);
if (interaction.deferred) {
await interaction.editReply(`Terjadi kesalahan: ${e.message}`);
}
}
}
else if (commandName === 'pause') {
distube.pause(interaction.guildId);
await interaction.reply('Musik dipause. ⏸️');
}
else if (commandName === 'resume') {
distube.resume(interaction.guildId);
await interaction.reply('Musik dilanjutkan. ▶️');
}
else if (commandName === 'skip') {
await distube.skip(interaction.guildId);
await interaction.reply('Lagu dilewati! ⏭️');
}
else if (commandName === 'stop') {
await distube.stop(interaction.guildId);
await interaction.reply('Musik dihentikan dan antrean dihapus. 👋');
}
} catch (error) {
logToFile(`Interaction Error (${commandName}): ${error.message}`);
if (!interaction.replied && !interaction.deferred) {
await interaction.reply({ content: `Terjadi kesalahan: ${error.message}`, ephemeral: true });
} else {
await interaction.editReply({ content: `Terjadi kesalahan: ${error.message}`, ephemeral: true });
}
}
});
// Prefix Command Handler (Legacy/Backup)
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 (command === 'play') {
const query = args.join(' ');
if (!voiceChannel) return message.reply('Anda harus berada di voice channel!');
if (!query) return message.reply('Masukkan judul atau link lagu!');
try {
await distube.play(voiceChannel, query, {
member: message.member,
textChannel: message.channel,
message
});
message.reply(`🔍 Mencari dan memutar: **${query}**...`);
} catch (e) {
logToFile(`Message Play Error: ${e.message}`);
message.reply(`❌ Error: ${e.message}`);
}
} else if (['skip', 'stop', 'pause', 'resume'].includes(command)) {
if (!voiceChannel) return message.reply('Anda harus berada di voice channel!');
const method = command === 'skip' ? 'skip' : (command === 'stop' ? 'stop' : (command === 'pause' ? 'pause' : 'resume'));
await distube[method](message.guildId);
message.reply(`${command} berhasil!`);
} else if (command === 'testsahur') {
if (!voiceChannel) return message.reply('Anda harus berada di voice channel!');
try {
await playLocalIndependent(voiceChannel);
message.reply('Mengetes suara sahur... 📢');
} catch (e) {
message.reply(`❌ Error: ${e.message}`);
}
}
} catch (error) {
logToFile(`Message Command Error: ${error.message}`);
message.reply(`❌ Error: ${error.message}`);
}
});
// DisTube Events
distube
.on('playSong', (queue, song) => {
queue.textChannel.send(`🎵 Sekarang memutar: **${song.name}** - \`${song.formatDuration}\`\nRequested by: ${song.user}`);
})
.on('addSong', (queue, song) => {
queue.textChannel.send(`✅ **${song.name}** ditambahkan ke antrean oleh ${song.user}`);
})
.on('error', (channel, e) => {
logToFile(`DisTube Error: ${e.message || e}`);
if (e.message && e.message.includes('Sign in to confirm youre not a bot')) {
if (channel) channel.send(`❌ YouTube memblokir akses bot. Silakan coba link dari SoundCloud atau Spotify.`);
} else if (channel) {
channel.send(`❌ Terjadi kesalahan: ${(e.message || e).toString().slice(0, 2000)}`);
}
});
// Handle uncaught errors to prevent crash
process.on('unhandledRejection', error => {
logToFile(`Unhandled Rejection: ${error.message}`);
console.error(error);
});
process.on('uncaughtException', error => {
logToFile(`Uncaught Exception: ${error.message}`);
console.error(error);
});
// Login
client.login(process.env.DISCORD_TOKEN);