Major upgrades and architectural improvements: - Upgrade Discord.js from v12 to v14.21.0 - Upgrade Node.js from 14 to 22 LTS - Switch to pnpm package manager - Complete rewrite with modern Discord API patterns New Features: - Hybrid command system: prefix commands + slash commands - /sfx slash command with autocomplete for sound discovery - Modern @discordjs/voice integration for audio - Improved voice connection management - Enhanced logging for SFX commands - Multi-stage Docker build for optimized images Technical Improvements: - Modular architecture with services and command handlers - Proper intent management for Discord gateway - Better error handling and logging - Hot-reload capability maintained - Environment variable support - Optimized Docker container with Alpine Linux Breaking Changes: - Moved main entry from index.js to src/index.js - Updated configuration structure for v14 compatibility - Replaced deprecated voice APIs with @discordjs/voice - Updated audio dependencies (opus, ffmpeg) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
254 lines
6.6 KiB
JavaScript
254 lines
6.6 KiB
JavaScript
const {
|
|
Client,
|
|
Events,
|
|
EmbedBuilder,
|
|
REST,
|
|
Routes,
|
|
ActivityType,
|
|
} = require("discord.js");
|
|
const { generateDependencyReport } = require("@discordjs/voice");
|
|
const intents = require("./config/intents");
|
|
const config = require("./config/config");
|
|
const { randElement } = require("./utils/helpers");
|
|
|
|
// Log audio dependencies status
|
|
console.log("Audio Dependencies Status:");
|
|
console.log(generateDependencyReport());
|
|
|
|
// Initialize Discord client
|
|
const client = new Client({ intents });
|
|
|
|
// Services
|
|
const commandLoader = require("./services/commandLoader");
|
|
const schedulerService = require("./services/schedulerService");
|
|
|
|
// Command handlers
|
|
const prefixCommands = require("./commands/prefix");
|
|
const slashCommands = require("./commands/slash");
|
|
|
|
// Activity rotation
|
|
let activityInterval;
|
|
|
|
/**
|
|
* Set a random activity for the bot
|
|
*/
|
|
function setRandomActivity() {
|
|
const activity =
|
|
config.discord.activities?.length > 0
|
|
? randElement(config.discord.activities)
|
|
: "DESTROY ALL HUMANS";
|
|
|
|
console.log(`Setting Discord activity to: ${activity}`);
|
|
|
|
client.user.setActivity(activity, {
|
|
url: "https://twitch.tv/fgfm",
|
|
type: ActivityType.Streaming,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Register slash commands
|
|
*/
|
|
async function registerSlashCommands() {
|
|
const rest = new REST({ version: "10" }).setToken(config.discord.token);
|
|
|
|
try {
|
|
console.log("Started refreshing application (/) commands.");
|
|
|
|
// Get all slash command definitions
|
|
const commands = slashCommands.getSlashCommandDefinitions();
|
|
|
|
// Register commands for each guild
|
|
for (const guild of config.discord.guilds) {
|
|
await rest.put(
|
|
Routes.applicationGuildCommands(client.user.id, guild.id),
|
|
{ body: commands }
|
|
);
|
|
console.log(
|
|
`Registered slash commands for guild: ${guild.internalName || guild.id}`
|
|
);
|
|
}
|
|
|
|
console.log("Successfully reloaded application (/) commands.");
|
|
} catch (error) {
|
|
console.error("Error registering slash commands:", error);
|
|
}
|
|
}
|
|
|
|
// Client ready event
|
|
client.once(Events.ClientReady, async () => {
|
|
console.log(`✅ ${config.botName} is connected and ready!`);
|
|
console.log(`Logged in as ${client.user.tag}`);
|
|
console.log(`Serving ${client.guilds.cache.size} guild(s)`);
|
|
|
|
// Set initial activity
|
|
setRandomActivity();
|
|
|
|
// Rotate activity every hour
|
|
activityInterval = setInterval(() => {
|
|
setRandomActivity();
|
|
}, 3600 * 1000);
|
|
|
|
// Register slash commands
|
|
await registerSlashCommands();
|
|
|
|
// Initialize scheduled events
|
|
schedulerService.initialize(client, config);
|
|
});
|
|
|
|
// Message handler for prefix commands
|
|
client.on(Events.MessageCreate, async (message) => {
|
|
// Ignore bot messages
|
|
if (message.author.bot) return;
|
|
|
|
// Ignore DMs if not configured
|
|
if (!message.guild) return;
|
|
|
|
// Check if guild is configured
|
|
const guildConfig = config.discord.guilds.find(
|
|
(g) => g.id === message.guild.id
|
|
);
|
|
if (!guildConfig) return;
|
|
|
|
// Check blacklist
|
|
if (config.discord.blacklistedUsers?.includes(message.author.id)) return;
|
|
|
|
// Check for command prefix
|
|
if (!message.content.startsWith(guildConfig.prefix)) return;
|
|
|
|
// Parse command
|
|
const args = message.content
|
|
.slice(guildConfig.prefix.length)
|
|
.trim()
|
|
.split(/ +/);
|
|
const commandName = args.shift().toLowerCase();
|
|
|
|
console.log(
|
|
`Command '${commandName}' received in ${
|
|
guildConfig.internalName || message.guild.name
|
|
}#${message.channel.name} from @${message.author.username}`
|
|
);
|
|
|
|
try {
|
|
// Check for prefix commands
|
|
if (prefixCommands.has(commandName)) {
|
|
await prefixCommands.execute(commandName, message, args, guildConfig);
|
|
return;
|
|
}
|
|
|
|
// Check for static commands
|
|
if (commandLoader.hasStaticCommand(commandName)) {
|
|
const response = commandLoader.getStaticCommand(commandName);
|
|
const embed = new EmbedBuilder()
|
|
.setTitle(commandName)
|
|
.setColor(0x21c629)
|
|
.setDescription(response);
|
|
|
|
await message.channel.send({ embeds: [embed] });
|
|
return;
|
|
}
|
|
|
|
// Check for Ankhbot commands
|
|
if (commandLoader.hasAnkhbotCommand(commandName)) {
|
|
const response = commandLoader.getAnkhbotCommand(commandName);
|
|
const embed = new EmbedBuilder()
|
|
.setTitle(commandName)
|
|
.setColor(0x21c629)
|
|
.setDescription(response);
|
|
|
|
await message.channel.send({ embeds: [embed] });
|
|
return;
|
|
}
|
|
|
|
// Command not found - ignore silently
|
|
} catch (error) {
|
|
console.error(`Error executing command ${commandName}:`, error);
|
|
message
|
|
.reply("There was an error executing that command!")
|
|
.catch(console.error);
|
|
}
|
|
});
|
|
|
|
// Interaction handler for slash commands
|
|
client.on(Events.InteractionCreate, async (interaction) => {
|
|
if (!interaction.isChatInputCommand() && !interaction.isAutocomplete())
|
|
return;
|
|
|
|
// Get guild config
|
|
const guildConfig = config.discord.guilds.find(
|
|
(g) => g.id === interaction.guild.id
|
|
);
|
|
if (!guildConfig) return;
|
|
|
|
try {
|
|
if (interaction.isAutocomplete()) {
|
|
await slashCommands.handleAutocomplete(interaction, guildConfig);
|
|
} else if (interaction.isChatInputCommand()) {
|
|
await slashCommands.execute(
|
|
interaction.commandName,
|
|
interaction,
|
|
guildConfig
|
|
);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error handling interaction:", error);
|
|
|
|
if (interaction.isChatInputCommand() && !interaction.replied) {
|
|
await interaction
|
|
.reply({
|
|
content: "There was an error executing this command!",
|
|
ephemeral: true,
|
|
})
|
|
.catch(console.error);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Handle new guild members
|
|
client.on(Events.GuildMemberAdd, (member) => {
|
|
// Check if guild is configured
|
|
const guildConfig = config.discord.guilds.find(
|
|
(g) => g.id === member.guild.id
|
|
);
|
|
if (!guildConfig) return;
|
|
|
|
console.log(
|
|
`A new member has joined '${member.guild.name}': ${member.displayName}`
|
|
);
|
|
});
|
|
|
|
// Handle guild becoming unavailable
|
|
client.on(Events.GuildUnavailable, (guild) => {
|
|
console.log(
|
|
`Guild '${guild.name}' is no longer available! Most likely due to server outage.`
|
|
);
|
|
});
|
|
|
|
// Debug logging
|
|
client.on("debug", (info) => {
|
|
if (config.debug === true) {
|
|
console.log(`[${new Date().toISOString()}] DEBUG: ${info}`);
|
|
}
|
|
});
|
|
|
|
// Error handling
|
|
client.on("error", console.error);
|
|
|
|
// Process error handling
|
|
process.on("unhandledRejection", console.error);
|
|
|
|
// Graceful shutdown
|
|
process.on("SIGTERM", () => {
|
|
console.log("SIGTERM signal received, shutting down gracefully...");
|
|
|
|
if (activityInterval) {
|
|
clearInterval(activityInterval);
|
|
}
|
|
|
|
client.destroy();
|
|
process.exit(0);
|
|
});
|
|
|
|
// Login to Discord
|
|
client.login(config.discord.token);
|