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.
This commit is contained in:
126
apps/bot/src/modules/birthdays/commands.ts
Normal file
126
apps/bot/src/modules/birthdays/commands.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
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];
|
||||
Reference in New Issue
Block a user