38465-vm/bot/index.js
Flatlogic Bot f2d5361ba4 V6
2026-02-16 07:47:49 +00:00

332 lines
12 KiB
JavaScript
Raw 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.

const { Client, GatewayIntentBits, Events, EmbedBuilder, PermissionsBitField } = require('discord.js');
const {
joinVoiceChannel,
createAudioPlayer,
createAudioResource,
AudioPlayerStatus,
StreamType,
VoiceConnectionStatus
} = require('@discordjs/voice');
const { DisTube } = require('distube');
const { YtDlpPlugin } = require('@distube/yt-dlp');
const { SoundCloudPlugin } = require('@distube/soundcloud');
const { SpotifyPlugin } = require('@distube/spotify');
const { CronJob } = require('cron');
const path = require('path');
const fs = require('fs');
// Load environment variables
require('dotenv').config({ path: path.join(__dirname, '.env') });
// Setup FFMPEG
const ffmpegStatic = require('ffmpeg-static');
process.env.FFMPEG_PATH = ffmpegStatic;
const ffmpegDir = path.dirname(ffmpegStatic);
if (!process.env.PATH.includes(ffmpegDir)) {
process.env.PATH = `${ffmpegDir}${path.delimiter}${process.env.PATH}`;
}
// Generate dependency report for debugging
try {
const { generateDependencyReport } = require('@discordjs/voice');
console.log("Dependency Report:\n" + generateDependencyReport());
} catch (e) {}
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildVoiceStates,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent
]
});
// 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, {
plugins: [
new SoundCloudPlugin(),
new SpotifyPlugin(),
new YtDlpPlugin(),
],
emitNewSongOnly: true,
emitAddSongWhenCreatingQueue: false,
});
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 playLocalFile(voiceChannel) {
const connection = joinVoiceChannel({
channelId: voiceChannel.id,
guildId: voiceChannel.guild.id,
adapterCreator: voiceChannel.guild.voiceAdapterCreator,
selfDeaf: false,
selfMute: false,
});
const player = createAudioPlayer();
player.on('error', error => {
logToFile(`Audio Player Error: ${error.message}`);
});
const resource = createAudioResource(AUDIO_PATH, {
inputType: StreamType.Arbitrary,
inlineVolume: true,
});
resource.volume.setVolume(1.0);
player.play(resource);
connection.subscribe(player);
player.on(AudioPlayerStatus.Idle, () => {
// connection.destroy();
});
return connection;
}
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 {
const connection = joinVoiceChannel({
channelId: channel.id,
guildId: guild.id,
adapterCreator: guild.voiceAdapterCreator,
selfDeaf: false,
selfMute: false,
});
const player = createAudioPlayer();
const resource = createAudioResource(AUDIO_PATH, {
inputType: StreamType.Arbitrary,
inlineVolume: true,
});
resource.volume.setVolume(1.0);
player.play(resource);
connection.subscribe(player);
logToFile('Alarm playing with custom audio resource.');
} catch (err) {
logToFile(`Alarm join 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();
joinVoiceChannel({
channelId: voiceChannel.id,
guildId: voiceChannel.guild.id,
adapterCreator: voiceChannel.guild.voiceAdapterCreator,
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 using custom resource: ${AUDIO_PATH}`);
try {
await playLocalFile(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') {
const query = interaction.options.getString('query');
if (!voiceChannel) return interaction.reply({ content: 'Anda harus berada di voice channel!', ephemeral: true });
await interaction.deferReply();
logToFile(`Fetching stream for: ${query}`);
// Ensure connection with specific settings
joinVoiceChannel({
channelId: voiceChannel.id,
guildId: voiceChannel.guild.id,
adapterCreator: voiceChannel.guild.voiceAdapterCreator,
selfDeaf: false,
selfMute: false,
});
distube.play(voiceChannel, query, {
member: interaction.member,
textChannel: interaction.channel,
interaction
}).catch(e => {
logToFile(`Play Error: ${e.message}`);
interaction.editReply(`Terjadi kesalahan saat memutar: ${e.message}`);
});
await interaction.editReply(`Mencari: **${query}**... 🔍`);
}
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!');
joinVoiceChannel({
channelId: voiceChannel.id,
guildId: voiceChannel.guild.id,
adapterCreator: voiceChannel.guild.voiceAdapterCreator,
selfDeaf: false,
selfMute: false,
});
distube.play(voiceChannel, query, {
member: message.member,
textChannel: message.channel,
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 playLocalFile(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}`);
if (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.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);