Files
Nexumi/apps/bot/src/index.ts
TheOnlyMace 4e135bcf43 Enhance component message handling and introduce new features
- Added `ComponentMessageBinding` model to manage message components in the database.
- Updated `WelcomeConfig`, `Tag`, and `ScheduledMessage` models to support new component fields.
- Integrated component handling in various modules, including Scheduler and Tags, to improve message customization.
- Enhanced user experience by implementing new commands for creating and managing component messages in the utility module.
- Updated localization files to reflect new component-related features and improve user guidance.
2026-07-22 22:55:37 +02:00

256 lines
8.2 KiB
TypeScript

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);
}