Files
Nexumi/apps/bot/src/modules/birthdays/commands.ts
smueller a583db7a15 Add giveaway, ticket, self-role, starboard, and suggestion features to the bot
- Introduced new models in the Prisma schema for giveaways, tickets, self-role panels, starboard configurations, and suggestions, enhancing the bot's functionality.
- Implemented job handling for self-role expiration and ticket management, improving user experience.
- Updated command localization to support new features in both German and English.
- Enhanced the job processing system to include dedicated workers for managing tickets and self-role expirations.
- Documented the new features and their setup processes in PHASE-TRACKING.md for clarity on implementation steps.
2026-07-22 13:08:37 +02:00

127 lines
4.4 KiB
TypeScript

import { PermissionFlagsBits } from 'discord.js';
import { t, tf } from '@nexumi/shared';
import type { SlashCommand } from '../../types.js';
import { getGuildLocale } from '../../i18n.js';
import { requirePermission } from '../../permissions.js';
import { birthdayCommandData } from './command-definitions.js';
import {
BirthdaySetSchema,
daysUntilBirthday,
getBirthdayConfig,
isValidTimezone,
listBirthdays,
removeBirthday,
setBirthday,
updateBirthdayConfig
} from './service.js';
const birthdayCommand: SlashCommand = {
data: birthdayCommandData,
async execute(interaction, context) {
const locale = await getGuildLocale(context.prisma, interaction.guildId);
if (!interaction.inGuild()) {
await interaction.reply({ content: t(locale, 'generic.guildOnly'), ephemeral: true });
return;
}
const guildId = interaction.guildId!;
const sub = interaction.options.getSubcommand();
if (sub === 'setup') {
if (!(await requirePermission(interaction, PermissionFlagsBits.ManageGuild, locale))) {
return;
}
const channel = interaction.options.getChannel('channel', true);
const role = interaction.options.getRole('role');
const timezone = interaction.options.getString('timezone') ?? 'Europe/Berlin';
const message = interaction.options.getString('message');
if (!isValidTimezone(timezone)) {
await interaction.reply({ content: t(locale, 'birthdays.error.invalidTimezone'), ephemeral: true });
return;
}
await updateBirthdayConfig(context.prisma, guildId, {
enabled: true,
channelId: channel.id,
roleId: role?.id ?? null,
message: message ?? null,
timezone
});
await interaction.reply({ content: t(locale, 'birthdays.setup.done'), ephemeral: true });
return;
}
const config = await getBirthdayConfig(context.prisma, guildId);
if (sub === 'set') {
const month = interaction.options.getInteger('month', true);
const day = interaction.options.getInteger('day', true);
const year = interaction.options.getInteger('year') ?? undefined;
const parsed = BirthdaySetSchema.safeParse({ month, day, year });
if (!parsed.success) {
await interaction.reply({ content: t(locale, 'birthdays.error.invalidDate'), ephemeral: true });
return;
}
try {
await setBirthday(context.prisma, guildId, interaction.user.id, parsed.data);
} catch (error) {
if (error instanceof Error && error.message === 'invalid_date') {
await interaction.reply({ content: t(locale, 'birthdays.error.invalidDate'), ephemeral: true });
return;
}
throw error;
}
await interaction.reply({
content: tf(locale, 'birthdays.set.success', { month, day, year: year ?? '—' }),
ephemeral: true
});
return;
}
if (sub === 'remove') {
const removed = await removeBirthday(context.prisma, guildId, interaction.user.id);
if (!removed) {
await interaction.reply({ content: t(locale, 'birthdays.remove.none'), ephemeral: true });
return;
}
await interaction.reply({ content: t(locale, 'birthdays.remove.success'), ephemeral: true });
return;
}
if (sub === 'list' || sub === 'next') {
const entries = await listBirthdays(context.prisma, guildId, config.timezone);
if (entries.length === 0) {
await interaction.reply({ content: t(locale, 'birthdays.list.empty'), ephemeral: true });
return;
}
const slice = sub === 'next' ? entries.slice(0, 10) : entries.slice(0, 25);
const lines = slice.map((entry, index) => {
const days = daysUntilBirthday(entry.month, entry.day, config.timezone);
const dateLabel = `${String(entry.day).padStart(2, '0')}.${String(entry.month).padStart(2, '0')}`;
const yearLabel = entry.year ? ` (${entry.year})` : '';
return tf(locale, 'birthdays.list.line', {
rank: index + 1,
user: `<@${entry.userId}>`,
date: `${dateLabel}${yearLabel}`,
days
});
});
const header = t(locale, sub === 'next' ? 'birthdays.next.header' : 'birthdays.list.header');
await interaction.reply({
content: `${header}\n${lines.join('\n')}`,
ephemeral: true
});
}
}
};
export const birthdayCommands: SlashCommand[] = [birthdayCommand];