fix: Giveaway bugs

This commit is contained in:
smueller
2026-07-24 10:33:56 +02:00
parent bd1b55fff7
commit f2406fb6d7
7 changed files with 74 additions and 25 deletions

View File

@@ -13,7 +13,7 @@ import { completeVerification } from './modules/verification/handlers.js';
import { closePoll } from './modules/utility/poll.js';
import { sendReminder } from './modules/utility/reminders.js';
import { expireSelfRole } from './modules/selfroles/service.js';
import { endGiveaway, runGiveawayCreateJob } from './modules/giveaways/index.js';
import { endGiveaway, runGiveawayCreateJob, GiveawayError } from './modules/giveaways/index.js';
import { closeInactiveTickets } from './modules/tickets/index.js';
import { expireBirthdayRole, runBirthdayCheck } from './modules/birthdays/index.js';
import { ensureStatsRecurringJobs, startStatsWorker } from './modules/stats/index.js';
@@ -303,7 +303,17 @@ export function startWorkers(context: BotContext): Worker[] {
giveawayQueueName,
async (job) => {
if (job.name === 'giveawayEnd') {
await endGiveaway(context, (job.data as GiveawayEndJob).giveawayId);
try {
await endGiveaway(context, (job.data as GiveawayEndJob).giveawayId);
} catch (error) {
if (
error instanceof GiveawayError &&
(error.code === 'already_ended' || error.code === 'not_found')
) {
return;
}
throw error;
}
return;
}
if (job.name === 'giveawayCreate') {

View File

@@ -1,3 +1,3 @@
export { giveawayCommands } from './commands.js';
export { isGiveawayButton, handleGiveawayButton } from './buttons.js';
export { endGiveaway, scheduleGiveawayEnd, runGiveawayCreateJob } from './service.js';
export { endGiveaway, scheduleGiveawayEnd, runGiveawayCreateJob, GiveawayError } from './service.js';

View File

@@ -59,6 +59,11 @@ function requirementLines(locale: 'de' | 'en', giveaway: Giveaway): string[] {
export function buildGiveawayEmbed(locale: 'de' | 'en', giveaway: Giveaway): EmbedBuilder {
const ended = giveaway.endedAt !== null;
const endsAtUnix = Math.floor(giveaway.endsAt.getTime() / 1000);
// Discord does not parse <t:…> timestamps in embed footers — only in
// description/fields/content. Keep relative + absolute for clarity.
const endsAtText = `<t:${endsAtUnix}:R> (<t:${endsAtUnix}:f>)`;
const embed = new EmbedBuilder()
.setTitle(t(locale, 'giveaway.embed.title'))
.setColor(ended ? 0x6b7280 : 0x6366f1)
@@ -86,19 +91,17 @@ export function buildGiveawayEmbed(locale: 'de' | 'en', giveaway: Giveaway): Emb
);
embed.setFooter({ text: t(locale, 'giveaway.embed.ended') });
} else if (giveaway.paused) {
embed.setDescription(t(locale, 'giveaway.embed.paused'));
embed.setFooter({
text: tf(locale, 'giveaway.embed.endsAt', {
time: `<t:${Math.floor(giveaway.endsAt.getTime() / 1000)}:R>`
})
});
embed.setDescription(
`${t(locale, 'giveaway.embed.paused')}\n\n${tf(locale, 'giveaway.embed.endsAt', {
time: endsAtText
})}`
);
} else {
embed.setDescription(t(locale, 'giveaway.embed.active'));
embed.setFooter({
text: tf(locale, 'giveaway.embed.endsAt', {
time: `<t:${Math.floor(giveaway.endsAt.getTime() / 1000)}:R>`
})
});
embed.setDescription(
`${t(locale, 'giveaway.embed.active')}\n\n${tf(locale, 'giveaway.embed.endsAt', {
time: endsAtText
})}`
);
}
const requirements = requirementLines(locale, giveaway);
@@ -313,10 +316,13 @@ async function dmWinners(
}
}
export async function endGiveaway(context: BotContext, giveawayId: string): Promise<void> {
export async function endGiveaway(context: BotContext, giveawayId: string): Promise<Giveaway> {
const giveaway = await context.prisma.giveaway.findUnique({ where: { id: giveawayId } });
if (!giveaway || giveaway.endedAt) {
return;
if (!giveaway) {
throw new GiveawayError('not_found');
}
if (giveaway.endedAt) {
throw new GiveawayError('already_ended');
}
await cancelGiveawayJob(giveawayId);
@@ -338,6 +344,7 @@ export async function endGiveaway(context: BotContext, giveawayId: string): Prom
if (winners.length > 0) {
await dmWinners(context, updated, winners, locale);
}
return updated;
}
export async function rerollGiveaway(