import { Client, Events, GatewayIntentBits, Partials, ShardingManager, type Interaction } from 'discord.js'; import { env } from './env.js'; import { logger } from './logger.js'; import { prisma } from './db.js'; import { redis } from './redis.js'; import { registerCommands, routeCommand, routeContextMenu } from './commands.js'; import { ensureRecurringJobs, startWorkers } from './jobs.js'; import type { BotContext } from './types.js'; import { startHealthServer } from './health.js'; import { handleModerationConfirmation, isModerationConfirmation } from './modules/moderation/confirmations.js'; import { getGuildLocale } from './i18n.js'; import { t } from '@nexumi/shared'; import { handleAutoModMessage } from './modules/automod/actions.js'; import { registerLoggingEvents } from './modules/logging/events.js'; import { registerWelcomeEvents } from './modules/welcome/events.js'; import { registerVerificationEvents } from './modules/verification/events.js'; import { handleVerificationButton } from './modules/verification/handlers.js'; import { isVerificationButton } from './modules/verification/service.js'; import { registerLevelingEvents } from './modules/leveling/events.js'; import { handleBlackjackButton, isBlackjackButton } from './modules/economy/blackjack-buttons.js'; import { handleEmbedModal, handlePollButton, isEmbedModal, isPollButton, registerUtilityEvents } from './modules/utility/index.js'; import { handleFunButton, isFunButton } from './modules/fun/index.js'; import { handleGiveawayButton, isGiveawayButton } from './modules/giveaways/index.js'; import { handleTicketInteraction, isTicketInteraction, registerTicketEvents } from './modules/tickets/index.js'; import { handleSelfRoleInteraction, isSelfRoleInteraction, registerSelfRoleEvents } from './modules/selfroles/index.js'; import { registerTagEvents } from './modules/tags/index.js'; import { registerStarboardEvents } from './modules/starboard/index.js'; import { handleSuggestionButton, isSuggestionButton } from './modules/suggestions/index.js'; import { handleTempVoiceInteraction, isTempVoiceInteraction, isTempVoiceModal, registerTempVoiceEvents } from './modules/tempvoice/index.js'; import { registerStatsEvents } from './modules/stats/index.js'; import { handleGuildBackupButton, isGuildBackupButton } from './modules/guildbackup/index.js'; import { handleComponentsV2Interaction, isComponentsV2Interaction } from './modules/components-v2/index.js'; const isShard = process.argv.includes('--shard'); if (!isShard) { startHealthServer(); const manager = new ShardingManager(new URL('./index.js', import.meta.url).pathname, { token: env.BOT_TOKEN, totalShards: 'auto', shardArgs: ['--shard'] }); manager.on('shardCreate', (shard) => logger.info({ shardId: shard.id }, 'Shard spawned')); await manager.spawn(); } else { const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMessageReactions, GatewayIntentBits.GuildModeration, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildEmojisAndStickers, GatewayIntentBits.GuildInvites, GatewayIntentBits.MessageContent ], partials: [ Partials.Channel, Partials.Message, Partials.GuildMember, Partials.Reaction ] }); const context: BotContext = { client, prisma, redis }; startWorkers(context); await ensureRecurringJobs(); registerLoggingEvents(context); registerWelcomeEvents(context); registerVerificationEvents(context); registerLevelingEvents(context); registerUtilityEvents(context); registerTicketEvents(context); registerSelfRoleEvents(client, context); registerTagEvents(client, context); registerStarboardEvents(context); registerTempVoiceEvents(context); registerStatsEvents(context); client.on(Events.ClientReady, async () => { logger.info({ tag: client.user?.tag }, 'Bot ready'); try { await registerCommands(); } catch (error) { logger.error({ error }, 'Failed to register application commands'); } try { const { applyBotPresence, publishBotStatus } = await import('./presence.js'); await applyBotPresence(context); await publishBotStatus(context); } catch (error) { logger.warn({ error }, 'Failed to apply initial bot presence/status'); } }); client.on(Events.MessageCreate, async (message) => { try { await handleAutoModMessage(context, message); } catch (error) { logger.error({ error, messageId: message.id }, 'AutoMod message handler failed'); } }); client.on(Events.InteractionCreate, async (interaction: Interaction) => { try { if (interaction.isChatInputCommand()) { await routeCommand(interaction, context); return; } if (interaction.isMessageContextMenuCommand()) { await routeContextMenu(interaction, context); return; } if (interaction.isButton() && isModerationConfirmation(interaction.customId)) { await handleModerationConfirmation(interaction, context); return; } if (interaction.isButton() && isVerificationButton(interaction.customId)) { await handleVerificationButton(interaction, context); return; } if (interaction.isButton() && isBlackjackButton(interaction.customId)) { await handleBlackjackButton(interaction, context); return; } if (interaction.isButton() && isPollButton(interaction.customId)) { await handlePollButton(interaction, context); return; } if (interaction.isButton() && isFunButton(interaction.customId)) { await handleFunButton(interaction, context); return; } if (interaction.isButton() && isGiveawayButton(interaction.customId)) { await handleGiveawayButton(interaction, context); return; } if ( (interaction.isButton() || interaction.isStringSelectMenu() || interaction.isModalSubmit()) && isTicketInteraction(interaction.customId) ) { await handleTicketInteraction(interaction, context); return; } if ( (interaction.isButton() || interaction.isStringSelectMenu()) && isSelfRoleInteraction(interaction.customId) ) { await handleSelfRoleInteraction(interaction, context); return; } if (interaction.isButton() && isSuggestionButton(interaction.customId)) { await handleSuggestionButton(interaction, context); return; } if (interaction.isButton() && isTempVoiceInteraction(interaction.customId)) { await handleTempVoiceInteraction(interaction, context); return; } if (interaction.isModalSubmit() && isTempVoiceModal(interaction.customId)) { await handleTempVoiceInteraction(interaction, context); return; } if (interaction.isButton() && isGuildBackupButton(interaction.customId)) { await handleGuildBackupButton(interaction, context); return; } if (interaction.isModalSubmit() && isEmbedModal(interaction.customId)) { await handleEmbedModal(interaction, context); return; } if ( (interaction.isButton() || interaction.isStringSelectMenu() || interaction.isUserSelectMenu() || interaction.isRoleSelectMenu() || interaction.isChannelSelectMenu() || interaction.isMentionableSelectMenu()) && isComponentsV2Interaction(interaction.customId) ) { await handleComponentsV2Interaction(interaction, context); return; } } catch (error) { logger.error({ error }, 'Interaction handling failed'); const locale = await getGuildLocale(context.prisma, interaction.guildId); const message = t(locale, 'generic.error'); if (interaction.isRepliable()) { if (interaction.replied || interaction.deferred) { await interaction.followUp({ content: message, ephemeral: true }); } else { await interaction.reply({ content: message, ephemeral: true }); } } } }); await client.login(env.BOT_TOKEN); }