- 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.
127 lines
4.4 KiB
TypeScript
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];
|