- Added functionality to activate and deactivate lockdowns via the automod worker, responding to specific job names. - Enhanced the anti-raid join handler to apply a verification role when lockdown is active and the anti-raid action is set to VERIFY. - Updated the automod configuration to ensure default rules are created if missing, improving the setup process for new guilds. - Introduced new API endpoints for managing cases and warnings, allowing for soft deletion and reason updates. - Enhanced the moderation dashboard to include warnings management, improving user experience in moderation tasks. - Updated localization files to reflect new features and improve user guidance in both English and German.
154 lines
5.2 KiB
TypeScript
154 lines
5.2 KiB
TypeScript
import { Queue, QueueEvents } from 'bullmq';
|
|
import { redis } from './redis';
|
|
|
|
/**
|
|
* BullMQ queue/job names mirrored 1:1 from `apps/bot/src/queues.ts` and
|
|
* `apps/bot/src/jobs.ts`. The WebUI has no discord.js Client, so any
|
|
* dashboard action that needs to touch Discord (posting a giveaway message,
|
|
* drawing winners, restoring a guild backup) is enqueued here and picked up
|
|
* by the bot's existing workers - the WebUI never talks to Discord directly
|
|
* for these actions.
|
|
*
|
|
* Queues and QueueEvents are created lazily on first use. Eager construction
|
|
* at import time would open Redis connections during `next build` (where
|
|
* Redis is intentionally unavailable), flooding the build log with
|
|
* ECONNREFUSED errors.
|
|
*/
|
|
export const giveawayQueueName = 'giveaways';
|
|
export const scheduleQueueName = 'schedules';
|
|
export const guildBackupQueueName = 'guild-backups';
|
|
export const suggestionsQueueName = 'suggestions';
|
|
export const messagesQueueName = 'messages';
|
|
export const verificationQueueName = 'verification';
|
|
export const automodQueueName = 'automod';
|
|
|
|
const globalForQueues = globalThis as unknown as {
|
|
__nexumiGiveawayQueue?: Queue;
|
|
__nexumiScheduleQueue?: Queue;
|
|
__nexumiGuildBackupQueue?: Queue;
|
|
__nexumiSuggestionsQueue?: Queue;
|
|
__nexumiMessagesQueue?: Queue;
|
|
__nexumiVerificationQueue?: Queue;
|
|
__nexumiAutomodQueue?: Queue;
|
|
__nexumiGiveawayQueueEvents?: QueueEvents;
|
|
__nexumiGuildBackupQueueEvents?: QueueEvents;
|
|
__nexumiSuggestionsQueueEvents?: QueueEvents;
|
|
__nexumiMessagesQueueEvents?: QueueEvents;
|
|
};
|
|
|
|
function queueConnection() {
|
|
return redis;
|
|
}
|
|
|
|
/** QueueEvents needs its own connection; duplicate inherits lazyConnect. */
|
|
function queueEventsConnection() {
|
|
return redis.duplicate();
|
|
}
|
|
|
|
export function getGiveawayQueue(): Queue {
|
|
globalForQueues.__nexumiGiveawayQueue ??= new Queue(giveawayQueueName, {
|
|
connection: queueConnection()
|
|
});
|
|
return globalForQueues.__nexumiGiveawayQueue;
|
|
}
|
|
|
|
export function getScheduleQueue(): Queue {
|
|
globalForQueues.__nexumiScheduleQueue ??= new Queue(scheduleQueueName, {
|
|
connection: queueConnection()
|
|
});
|
|
return globalForQueues.__nexumiScheduleQueue;
|
|
}
|
|
|
|
export function getGuildBackupQueue(): Queue {
|
|
globalForQueues.__nexumiGuildBackupQueue ??= new Queue(guildBackupQueueName, {
|
|
connection: queueConnection()
|
|
});
|
|
return globalForQueues.__nexumiGuildBackupQueue;
|
|
}
|
|
|
|
export function getSuggestionsQueue(): Queue {
|
|
globalForQueues.__nexumiSuggestionsQueue ??= new Queue(suggestionsQueueName, {
|
|
connection: queueConnection()
|
|
});
|
|
return globalForQueues.__nexumiSuggestionsQueue;
|
|
}
|
|
|
|
export function getMessagesQueue(): Queue {
|
|
globalForQueues.__nexumiMessagesQueue ??= new Queue(messagesQueueName, {
|
|
connection: queueConnection()
|
|
});
|
|
return globalForQueues.__nexumiMessagesQueue;
|
|
}
|
|
|
|
export function getVerificationQueue(): Queue {
|
|
globalForQueues.__nexumiVerificationQueue ??= new Queue(verificationQueueName, {
|
|
connection: queueConnection()
|
|
});
|
|
return globalForQueues.__nexumiVerificationQueue;
|
|
}
|
|
|
|
export function getAutomodQueue(): Queue {
|
|
globalForQueues.__nexumiAutomodQueue ??= new Queue(automodQueueName, {
|
|
connection: queueConnection()
|
|
});
|
|
return globalForQueues.__nexumiAutomodQueue;
|
|
}
|
|
|
|
/**
|
|
* QueueEvents instances are only needed for jobs where the dashboard needs
|
|
* to wait for the bot to finish (e.g. ending a giveaway so the winners list
|
|
* is immediately visible).
|
|
*/
|
|
export function getGiveawayQueueEvents(): QueueEvents {
|
|
globalForQueues.__nexumiGiveawayQueueEvents ??= new QueueEvents(giveawayQueueName, {
|
|
connection: queueEventsConnection()
|
|
});
|
|
return globalForQueues.__nexumiGiveawayQueueEvents;
|
|
}
|
|
|
|
export function getGuildBackupQueueEvents(): QueueEvents {
|
|
globalForQueues.__nexumiGuildBackupQueueEvents ??= new QueueEvents(guildBackupQueueName, {
|
|
connection: queueEventsConnection()
|
|
});
|
|
return globalForQueues.__nexumiGuildBackupQueueEvents;
|
|
}
|
|
|
|
export function getSuggestionsQueueEvents(): QueueEvents {
|
|
globalForQueues.__nexumiSuggestionsQueueEvents ??= new QueueEvents(suggestionsQueueName, {
|
|
connection: queueEventsConnection()
|
|
});
|
|
return globalForQueues.__nexumiSuggestionsQueueEvents;
|
|
}
|
|
|
|
export function getMessagesQueueEvents(): QueueEvents {
|
|
globalForQueues.__nexumiMessagesQueueEvents ??= new QueueEvents(messagesQueueName, {
|
|
connection: queueEventsConnection()
|
|
});
|
|
return globalForQueues.__nexumiMessagesQueueEvents;
|
|
}
|
|
|
|
const DEFAULT_WAIT_TIMEOUT_MS = 15_000;
|
|
|
|
/**
|
|
* Adds a job and waits (bounded) for the bot worker to finish it, so the
|
|
* dashboard can immediately reflect the result instead of polling. Falls
|
|
* back to "not confirmed" (does not throw) on timeout - the DB row created
|
|
* by the worker is still the source of truth and will show up on next load.
|
|
*/
|
|
export async function addJobAndAwait<T = unknown>(
|
|
queue: Queue,
|
|
queueEvents: QueueEvents,
|
|
jobName: string,
|
|
data: unknown,
|
|
options: Parameters<Queue['add']>[2] = {},
|
|
timeoutMs = DEFAULT_WAIT_TIMEOUT_MS
|
|
): Promise<{ confirmed: boolean; result: T | null }> {
|
|
const job = await queue.add(jobName, data, options);
|
|
try {
|
|
const result = (await job.waitUntilFinished(queueEvents, timeoutMs)) as T;
|
|
return { confirmed: true, result };
|
|
} catch {
|
|
return { confirmed: false, result: null };
|
|
}
|
|
}
|