207 lines
7.9 KiB
JavaScript
207 lines
7.9 KiB
JavaScript
const { Client, GatewayIntentBits, EmbedBuilder, PermissionsBitField, REST, Routes, SlashCommandBuilder } = require('discord.js');
|
|
const { DisTube } = require('distube');
|
|
const { YtDlpPlugin } = require('@distube/yt-dlp');
|
|
const { YouTubePlugin } = require('@distube/youtube');
|
|
const { GoogleGenerativeAI } = require('@google/generative-ai');
|
|
const express = require('express');
|
|
require('dotenv').config();
|
|
|
|
// 1. Keep-Alive Server (Port 8080)
|
|
const app = express();
|
|
app.get('/', (req, res) => res.send('Wizzy Bot is Online!'));
|
|
const PORT = process.env.PORT || 8080;
|
|
app.listen(PORT, () => console.log(`✅ Server Keep-Alive aktif di port ${PORT}`));
|
|
|
|
// 2. Gemini AI Setup
|
|
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
|
|
const model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash' });
|
|
|
|
// 3. Discord Client Setup
|
|
const client = new Client({
|
|
intents: [
|
|
GatewayIntentBits.Guilds,
|
|
GatewayIntentBits.GuildVoiceStates,
|
|
GatewayIntentBits.GuildMessages,
|
|
GatewayIntentBits.MessageContent
|
|
]
|
|
});
|
|
|
|
// 4. DisTube Setup (Music)
|
|
const distube = new DisTube(client, {
|
|
emitNewSongOnly: true,
|
|
emitAddSongWhenCreatingQueue: false,
|
|
emitAddListWhenCreatingQueue: false,
|
|
plugins: [
|
|
new YouTubePlugin(),
|
|
new YtDlpPlugin()
|
|
]
|
|
});
|
|
|
|
// 5. Slash Commands Definition
|
|
const commands = [
|
|
new SlashCommandBuilder()
|
|
.setName('wizzy')
|
|
.setDescription('Tanya Wizzy (Gemini AI)')
|
|
.addStringOption(option =>
|
|
option.setName('prompt')
|
|
.setDescription('Pertanyaanmu untuk Wizzy')
|
|
.setRequired(true)),
|
|
new SlashCommandBuilder()
|
|
.setName('play')
|
|
.setDescription('Putar musik dari judul atau link')
|
|
.addStringOption(option =>
|
|
option.setName('query')
|
|
.setDescription('Judul lagu atau link (YouTube/Spotify/SoundCloud)')
|
|
.setRequired(true)),
|
|
new SlashCommandBuilder()
|
|
.setName('skip')
|
|
.setDescription('Lewati lagu saat ini'),
|
|
new SlashCommandBuilder()
|
|
.setName('stop')
|
|
.setDescription('Berhentikan musik dan keluar dari voice channel'),
|
|
new SlashCommandBuilder()
|
|
.setName('queue')
|
|
.setDescription('Lihat daftar antrean lagu'),
|
|
new SlashCommandBuilder()
|
|
.setName('pause')
|
|
.setDescription('Jeda musik'),
|
|
new SlashCommandBuilder()
|
|
.setName('resume')
|
|
.setDescription('Lanjutkan musik'),
|
|
].map(command => command.toJSON());
|
|
|
|
// 6. Interaction Handling (Slash Commands)
|
|
client.on('interactionCreate', async interaction => {
|
|
// ⚡ INSTANT DEFER: Prevent 3s Timeout
|
|
if (interaction.isChatInputCommand()) {
|
|
await interaction.deferReply().catch(e => console.error('Defer Error:', e));
|
|
}
|
|
|
|
if (!interaction.isChatInputCommand()) return;
|
|
console.log(`--- Perintah masuk: /${interaction.commandName} ---`);
|
|
|
|
const { commandName } = interaction;
|
|
|
|
// --- WIZZY (GEMINI AI) HANDLER ---
|
|
if (commandName === 'wizzy') {
|
|
try {
|
|
const userInput = interaction.options.getString('prompt') ?? 'Halo!';
|
|
const result = await model.generateContent(userInput);
|
|
const response = result.response.text();
|
|
const reply = response.length > 1990 ? response.substring(0, 1990) + '...' : response;
|
|
await interaction.editReply(reply);
|
|
} catch (error) {
|
|
console.error('❌ Gemini Error:', error);
|
|
await interaction.editReply({ content: '⚠️ Wizzy mengalami error, coba lagi nanti.', ephemeral: true });
|
|
}
|
|
return;
|
|
}
|
|
|
|
// --- MUSIC HANDLERS ---
|
|
if (commandName === 'play') {
|
|
try {
|
|
const query = interaction.options.getString('query');
|
|
if (!interaction.member.voice.channel) {
|
|
return interaction.editReply('Kamu harus berada di voice channel!');
|
|
}
|
|
await interaction.editReply({ content: `🔍 Mencari: **${query}**...` });
|
|
await distube.play(interaction.member.voice.channel, query, {
|
|
textChannel: interaction.channel,
|
|
member: interaction.member,
|
|
interaction
|
|
});
|
|
} catch (error) {
|
|
console.error('Play Error:', error);
|
|
await interaction.editReply({ content: `❌ Error: ${error.message}` });
|
|
}
|
|
} else if (commandName === 'skip') {
|
|
try {
|
|
const queue = distube.getQueue(interaction.guild);
|
|
if (!queue) return interaction.editReply('❌ Tidak ada lagu!');
|
|
await distube.skip(interaction.guild);
|
|
await interaction.editReply('⏭️ Lagu dilewati!');
|
|
} catch (e) { await interaction.editReply(`❌ Error: ${e.message}`); }
|
|
} else if (commandName === 'stop') {
|
|
try {
|
|
await distube.stop(interaction.guild);
|
|
await interaction.editReply('⏹️ Musik berhenti!');
|
|
} catch (e) { await interaction.editReply(`❌ Error: ${e.message}`); }
|
|
} else if (commandName === 'pause') {
|
|
try {
|
|
distube.pause(interaction.guild);
|
|
await interaction.editReply('⏸️ Musik dijeda!');
|
|
} catch (e) { await interaction.editReply(`❌ Error: ${e.message}`); }
|
|
} else if (commandName === 'resume') {
|
|
try {
|
|
distube.resume(interaction.guild);
|
|
await interaction.editReply('▶️ Musik lanjut!');
|
|
} catch (e) { await interaction.editReply(`❌ Error: ${e.message}`); }
|
|
} else if (commandName === 'queue') {
|
|
try {
|
|
const queue = distube.getQueue(interaction.guild);
|
|
if (!queue) return interaction.editReply('📭 Antrean kosong!');
|
|
const q = queue.songs.map((song, i) => `${i === 0 ? '▶️' : `${i}.`} ${song.name}`).join('\n');
|
|
await interaction.editReply(`🎶 **Antrean:**\n${q.slice(0, 1900)}`);
|
|
} catch (e) { await interaction.editReply(`❌ Error: ${e.message}`); }
|
|
}
|
|
});
|
|
|
|
// 7. Message Handling (Prefix Command !wizzy)
|
|
client.on('messageCreate', async (message) => {
|
|
if (message.author.bot) return;
|
|
if (!message.content.startsWith('!wizzy ')) return;
|
|
|
|
const userInput = message.content.slice(7).trim();
|
|
if (!userInput) return message.reply('Tulis pertanyaanmu!');
|
|
|
|
await message.channel.sendTyping();
|
|
try {
|
|
const result = await model.generateContent(userInput);
|
|
const response = result.response.text();
|
|
const reply = response.length > 1990 ? response.substring(0, 1990) + '...' : response;
|
|
await message.reply(reply);
|
|
} catch (error) {
|
|
console.error('❌ Error Gemini (Prefix):', error);
|
|
await message.reply('⚠️ Terjadi error pada Wizzy!');
|
|
}
|
|
});
|
|
|
|
// 8. DisTube Events
|
|
distube
|
|
.on('playSong', (queue, song) => {
|
|
queue.textChannel.send(`🎶 Memutar: **${song.name}** (\`${song.formattedDuration}\`)`);
|
|
})
|
|
.on('addSong', (queue, song) => {
|
|
queue.textChannel.send(`✅ Ditambahkan: **${song.name}**`);
|
|
})
|
|
.on('error', (channel, e) => {
|
|
if (channel?.send) channel.send(`❌ DisTube Error: ${e.message.slice(0, 1000)}`);
|
|
console.error('DisTube Error:', e);
|
|
});
|
|
|
|
// 9. Ready Event & Command Registration
|
|
client.once('ready', async () => {
|
|
console.log(`✅ Wizzy aktif sebagai ${client.user.tag}`);
|
|
console.log('Bot siap memproses perintah!');
|
|
|
|
const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);
|
|
try {
|
|
console.log('Registering slash commands...');
|
|
await rest.put(
|
|
Routes.applicationCommands(client.user.id),
|
|
{ body: commands },
|
|
);
|
|
console.log('Successfully registered slash commands!');
|
|
} catch (error) {
|
|
console.error('Registration Error:', error);
|
|
}
|
|
});
|
|
|
|
// 10. Anti-Crash
|
|
process.on('unhandledRejection', e => console.error('Unhandled Rejection:', e));
|
|
process.on('uncaughtException', e => console.error('Uncaught Exception:', e));
|
|
|
|
// 11. Login
|
|
console.log('Logging in...');
|
|
client.login(process.env.DISCORD_TOKEN);
|