Implement verification panel synchronization with BullMQ integration

- Introduced a new `verificationPanelSync` job to handle posting and updating the verification panel in Discord channels via BullMQ.
- Enhanced the `syncVerificationPanel` function to manage panel updates and error handling, ensuring proper feedback for synchronization issues.
- Updated the WebUI to support verification panel management, including improved error messages for timeout and posting failures.
- Added localization entries for new error messages related to verification panel synchronization in both English and German.
- Refactored existing verification logic to accommodate the new synchronization process, improving overall functionality and user experience.
This commit is contained in:
TheOnlyMace
2026-07-25 15:55:13 +02:00
parent 67ac16d36b
commit ef1803f0ab
10 changed files with 278 additions and 64 deletions

View File

@@ -2,6 +2,21 @@ import type { CaptchaProvider, VerificationConfigDashboard, VerificationConfigDa
import type { Prisma } from '@prisma/client';
import { prisma } from '../prisma';
import { getAvailableCaptchaProviders } from '../captcha';
import {
addJobAndAwait,
getVerificationQueue,
getVerificationQueueEvents
} from '../queues';
export class VerificationPanelSyncError extends Error {
constructor(
message: string,
public readonly code: 'timeout' | 'not_posted' = 'not_posted'
) {
super(message);
this.name = 'VerificationPanelSyncError';
}
}
async function ensureVerificationConfig(guildId: string) {
await prisma.guild.upsert({ where: { id: guildId }, update: {}, create: { id: guildId } });
@@ -47,6 +62,46 @@ export async function getVerificationDashboard(guildId: string): Promise<{
};
}
/**
* Dashboard has no discord.js Client, so panel posting is delegated to the bot
* via the `verification` BullMQ queue (`verificationPanelSync`) — same pattern
* as self-role panels / giveaways.
*/
async function syncVerificationPanel(guildId: string): Promise<void> {
const { confirmed, result } = await addJobAndAwait<{
panelChannelId: string;
panelMessageId: string;
}>(
getVerificationQueue(),
getVerificationQueueEvents(),
'verificationPanelSync',
{ guildId },
{
jobId: `verification-panel-${guildId}-${Date.now()}`,
removeOnComplete: true,
removeOnFail: 25
},
30_000
);
if (!confirmed || !result?.panelMessageId) {
throw new VerificationPanelSyncError(
confirmed
? 'Verification panel job finished without posting a Discord message'
: 'The bot did not confirm the verification panel in time. It may still be posting — check the channel shortly.',
confirmed ? 'not_posted' : 'timeout'
);
}
const config = await prisma.verificationConfig.findUnique({ where: { guildId } });
if (!config?.panelMessageId) {
throw new VerificationPanelSyncError(
'Verification panel job finished without storing a Discord message id',
'not_posted'
);
}
}
export async function updateVerificationDashboard(
guildId: string,
patch: VerificationConfigDashboardPatch
@@ -71,5 +126,11 @@ export async function updateVerificationDashboard(
}
const updated = await prisma.verificationConfig.update({ where: { guildId }, data });
return toDashboard(updated, getAvailableCaptchaProviders());
const dashboard = toDashboard(updated, getAvailableCaptchaProviders());
if (dashboard.enabled && dashboard.channelId && dashboard.verifiedRoleId) {
await syncVerificationPanel(guildId);
}
return dashboard;
}