- 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.
256 lines
8.2 KiB
TypeScript
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);
|
|
}
|