Create index.ts
Browse files
index.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import config from './config.json' with { type: 'json' }; // Bun yêu cầu cú pháp này cho JSON
|
| 2 |
+
import { Client, GatewayIntentBits, Partials } from 'discord.js';
|
| 3 |
+
import { readdirSync } from 'fs';
|
| 4 |
+
import { join } from 'path';
|
| 5 |
+
import type { Command } from './types';
|
| 6 |
+
import { MusicQueue } from './utils/MusicQueue';
|
| 7 |
+
|
| 8 |
+
const commandsDir = join(import.meta.dir, 'Commands'); // Bun sử dụng import.meta.dir thay vì __dirname
|
| 9 |
+
const commands: Command[] = readdirSync(commandsDir)
|
| 10 |
+
.filter(file => file.endsWith('.ts') || file.endsWith('.js'))
|
| 11 |
+
.map(file => {
|
| 12 |
+
try {
|
| 13 |
+
const commandModule = require(join(commandsDir, file)); // Bun hỗ trợ require, nhưng ESM import tốt hơn
|
| 14 |
+
return commandModule.default;
|
| 15 |
+
} catch (error) {
|
| 16 |
+
console.error(`❌ Failed to load command ${file}:`, error);
|
| 17 |
+
return undefined;
|
| 18 |
+
}
|
| 19 |
+
})
|
| 20 |
+
.filter((cmd): cmd is Command => cmd !== undefined);
|
| 21 |
+
|
| 22 |
+
const clients: Client[] = [];
|
| 23 |
+
const queues = new Map<string, MusicQueue>(); // Quản lý hàng đợi nhạc theo guildId
|
| 24 |
+
|
| 25 |
+
config.tokens.forEach((token: string, index: number) => {
|
| 26 |
+
const client = new Client({
|
| 27 |
+
intents: [
|
| 28 |
+
GatewayIntentBits.Guilds,
|
| 29 |
+
GatewayIntentBits.GuildMessages,
|
| 30 |
+
GatewayIntentBits.MessageContent,
|
| 31 |
+
GatewayIntentBits.DirectMessages,
|
| 32 |
+
GatewayIntentBits.GuildVoiceStates, // Thêm intent cho voice
|
| 33 |
+
],
|
| 34 |
+
partials: [Partials.Channel],
|
| 35 |
+
});
|
| 36 |
+
|
| 37 |
+
client.once('ready', () => {
|
| 38 |
+
console.log(`✅ Client ${index + 1} - ${client.user?.username} is ready!`);
|
| 39 |
+
clients.push(client);
|
| 40 |
+
});
|
| 41 |
+
|
| 42 |
+
// Xử lý prefix commands
|
| 43 |
+
client.on('messageCreate', async message => {
|
| 44 |
+
if (message.author.bot || !message.content.startsWith(config.PREFIX)) return;
|
| 45 |
+
|
| 46 |
+
const args = message.content.slice(config.PREFIX.length).trim().split(/\s+/);
|
| 47 |
+
const commandName = args.shift()?.toLowerCase();
|
| 48 |
+
const command = commands.find(cmd => cmd?.data?.name === commandName);
|
| 49 |
+
|
| 50 |
+
if (!command) {
|
| 51 |
+
return message.reply(`❌ Lệnh \`${commandName}\` không tồn tại. Dùng \`${config.PREFIX}help\` để xem danh sách lệnh.`);
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
if (command.ownersOnly && !config.owners.includes(message.author.id)) {
|
| 55 |
+
return message.reply('⛔ Bạn không có quyền dùng lệnh này.');
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
try {
|
| 59 |
+
await command.execute(message, args, client);
|
| 60 |
+
} catch (error) {
|
| 61 |
+
console.error(`❌ Lỗi khi xử lý lệnh ${commandName}:`, error);
|
| 62 |
+
await message.reply('❌ Có lỗi xảy ra khi chạy lệnh.').catch(console.error);
|
| 63 |
+
}
|
| 64 |
+
});
|
| 65 |
+
|
| 66 |
+
// Auto-stop khi người dùng rời voice channel
|
| 67 |
+
client.on('voiceStateUpdate', (oldState, newState) => {
|
| 68 |
+
const queue = queues.get(oldState.guild.id);
|
| 69 |
+
if (!queue || !queue.connection) return;
|
| 70 |
+
|
| 71 |
+
const botVoiceChannel = queue.connection.joinConfig.channelId;
|
| 72 |
+
if (oldState.channelId === botVoiceChannel && newState.channelId !== botVoiceChannel) {
|
| 73 |
+
const channel = oldState.guild.channels.cache.get(botVoiceChannel);
|
| 74 |
+
if (channel) {
|
| 75 |
+
const members = channel.members.filter(member => !member.user.bot);
|
| 76 |
+
if (members.size === 0) {
|
| 77 |
+
queue.songs = [];
|
| 78 |
+
queue.playing = false;
|
| 79 |
+
queue.currentSong = null;
|
| 80 |
+
queue.player.stop();
|
| 81 |
+
if (queue.connection) {
|
| 82 |
+
queue.connection.destroy();
|
| 83 |
+
queue.connection = null;
|
| 84 |
+
}
|
| 85 |
+
console.log(`Auto-stopped music in guild ${oldState.guild.name} - no users in voice channel`);
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
}
|
| 89 |
+
});
|
| 90 |
+
|
| 91 |
+
client.login(token).catch(error => {
|
| 92 |
+
console.error(`❌ Failed to login Client ${index + 1}:`, error);
|
| 93 |
+
});
|
| 94 |
+
|
| 95 |
+
});
|
| 96 |
+
|
| 97 |
+
export { clients, commands, queues }; // Xuất queues để sử dụng trong các lệnh
|
| 98 |
+
|
| 99 |
+
process.on('unhandledRejection', error => {
|
| 100 |
+
console.error('❗ Unhandled promise rejection:', error);
|
| 101 |
+
});
|
| 102 |
+
process.on('uncaughtException', error => {
|
| 103 |
+
console.error('❗ Uncaught exception:', error);
|
| 104 |
+
});
|