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:
166
apps/bot/src/modules/giveaways/commands.ts
Normal file
166
apps/bot/src/modules/giveaways/commands.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
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 { parseDuration } from '../moderation/duration.js';
|
||||
import { giveawayCommandData } from './command-definitions.js';
|
||||
import {
|
||||
deleteGiveaway,
|
||||
endGiveaway,
|
||||
GiveawayError,
|
||||
listActiveGiveaways,
|
||||
pauseGiveaway,
|
||||
rerollGiveaway,
|
||||
startGiveaway
|
||||
} from './service.js';
|
||||
|
||||
async function ensureManageGuild(
|
||||
interaction: Parameters<SlashCommand['execute']>[0],
|
||||
locale: 'de' | 'en'
|
||||
): Promise<boolean> {
|
||||
if (!interaction.inGuild()) {
|
||||
await interaction.reply({ content: t(locale, 'generic.guildOnly'), ephemeral: true });
|
||||
return false;
|
||||
}
|
||||
return requirePermission(interaction, PermissionFlagsBits.ManageGuild, locale);
|
||||
}
|
||||
|
||||
const giveawayCommand: SlashCommand = {
|
||||
data: giveawayCommandData,
|
||||
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 sub = interaction.options.getSubcommand();
|
||||
|
||||
if (sub === 'start') {
|
||||
if (!(await ensureManageGuild(interaction, locale))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const durationInput = interaction.options.getString('duration', true);
|
||||
let durationMs: number;
|
||||
try {
|
||||
durationMs = parseDuration(durationInput);
|
||||
} catch {
|
||||
await interaction.reply({
|
||||
content: t(locale, 'giveaway.error.invalid_duration'),
|
||||
ephemeral: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (durationMs < 60_000) {
|
||||
await interaction.reply({
|
||||
content: t(locale, 'giveaway.error.duration_too_short'),
|
||||
ephemeral: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const channel = interaction.channel;
|
||||
if (!channel?.isTextBased() || channel.isDMBased()) {
|
||||
await interaction.reply({
|
||||
content: t(locale, 'giveaway.error.invalid_channel'),
|
||||
ephemeral: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const giveaway = await startGiveaway(
|
||||
context,
|
||||
{
|
||||
guildId: interaction.guildId!,
|
||||
channelId: channel.id,
|
||||
hostId: interaction.user.id,
|
||||
prize: interaction.options.getString('prize', true),
|
||||
winnerCount: interaction.options.getInteger('winners', true),
|
||||
durationMs,
|
||||
requiredRoleId: interaction.options.getRole('role')?.id,
|
||||
requiredLevel: interaction.options.getInteger('level'),
|
||||
requiredMemberDays: interaction.options.getInteger('member_days')
|
||||
},
|
||||
locale
|
||||
);
|
||||
|
||||
await interaction.reply({
|
||||
content: tf(locale, 'giveaway.started', { id: giveaway.id }),
|
||||
ephemeral: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await ensureManageGuild(interaction, locale))) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (sub === 'end') {
|
||||
const id = interaction.options.getString('id', true);
|
||||
await endGiveaway(context, id);
|
||||
await interaction.reply({ content: t(locale, 'giveaway.ended'), ephemeral: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub === 'reroll') {
|
||||
const id = interaction.options.getString('id', true);
|
||||
const updated = await rerollGiveaway(context, id, locale);
|
||||
await interaction.reply({
|
||||
content: tf(locale, 'giveaway.rerolled', {
|
||||
winners: updated.winners.map((w) => `<@${w}>`).join(', ') || '—'
|
||||
}),
|
||||
ephemeral: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub === 'list') {
|
||||
const active = await listActiveGiveaways(context, interaction.guildId!);
|
||||
if (active.length === 0) {
|
||||
await interaction.reply({ content: t(locale, 'giveaway.list.empty'), ephemeral: true });
|
||||
return;
|
||||
}
|
||||
const lines = active.map(
|
||||
(g) =>
|
||||
`\`${g.id}\` — **${g.prize}** (${g.entrants.length} ${t(locale, 'giveaway.list.entrants')}, <t:${Math.floor(g.endsAt.getTime() / 1000)}:R>)`
|
||||
);
|
||||
await interaction.reply({
|
||||
content: `${t(locale, 'giveaway.list.header')}\n${lines.join('\n')}`,
|
||||
ephemeral: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub === 'delete') {
|
||||
const id = interaction.options.getString('id', true);
|
||||
await deleteGiveaway(context, id);
|
||||
await interaction.reply({ content: t(locale, 'giveaway.deleted'), ephemeral: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub === 'pause') {
|
||||
const id = interaction.options.getString('id', true);
|
||||
const updated = await pauseGiveaway(context, id, locale);
|
||||
await interaction.reply({
|
||||
content: t(locale, updated.paused ? 'giveaway.paused' : 'giveaway.resumed'),
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof GiveawayError) {
|
||||
await interaction.reply({
|
||||
content: t(locale, `giveaway.error.${error.code}`),
|
||||
ephemeral: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const giveawayCommands = [giveawayCommand];
|
||||
Reference in New Issue
Block a user