This commit is contained in:
2026-04-20 18:05:36 +00:00
parent d73422555d
commit 33b1f276c6
26 changed files with 598 additions and 183 deletions

View File

@@ -25,6 +25,23 @@ const StaffNotification = mongoose.model('StaffNotification');
// In-memory cooldown map: `${userId}:${ticketId}` -> last notified timestamp
const replyCooldowns = new Map();
const REPLY_COOLDOWN_SWEEP_TTL_MS = 48 * 60 * 60 * 1000;
const REPLY_COOLDOWN_SWEEP_INTERVAL_MS = 6 * 60 * 60 * 1000;
function sweepReplyCooldowns(now = Date.now()) {
const cutoff = now - REPLY_COOLDOWN_SWEEP_TTL_MS;
for (const [key, ts] of replyCooldowns.entries()) {
if (ts < cutoff) replyCooldowns.delete(key);
}
}
function startSweeps(trackInterval) {
const handle = setInterval(() => sweepReplyCooldowns(), REPLY_COOLDOWN_SWEEP_INTERVAL_MS);
if (typeof handle.unref === 'function') handle.unref();
if (typeof trackInterval === 'function') trackInterval(handle);
return handle;
}
/**
* Notify the claiming staff member when a non-staff user replies.
* Respects the staff member's cooldownHours setting (default 1h).
@@ -72,11 +89,13 @@ async function notifyAllStaffUnclaimed(client) {
const sorted = [...thresholds].sort((a, b) => a - b);
const now = Date.now();
// Bounded per-tick: oldest-first, capped at 500. A backlog larger than 500
// gets drained in subsequent 30-minute ticks rather than one long run.
const unclaimedTickets = await Ticket.find({
status: 'open',
claimedBy: null,
createdAt: { $ne: null }
}).lean();
}).sort({ createdAt: 1 }).limit(500).lean();
if (unclaimedTickets.length === 0) return;
@@ -103,8 +122,7 @@ async function notifyAllStaffUnclaimed(client) {
const channelName = ticket.discordThreadId
? `<#${ticket.discordThreadId}>`
: `ticket #${ticket.ticketNumber}`;
const hoursAgo = Math.floor(ageHours);
const alertMsg = `Unclaimed ticket alert: ${channelName} has been unclaimed for ${hoursAgo}+ hour(s) (${highest}h threshold).`;
const alertMsg = `[${highest}h+ unclaimed] ${channelName}`;
for (const rec of staffRecords) {
const chan = await guild.channels.fetch(rec.channelId).catch(() => null);
@@ -122,4 +140,10 @@ async function notifyAllStaffUnclaimed(client) {
}
}
module.exports = { notifyStaffOfReply, notifyAllStaffUnclaimed };
module.exports = {
notifyStaffOfReply,
notifyAllStaffUnclaimed,
startSweeps,
sweepReplyCooldowns,
_internals: { replyCooldowns, REPLY_COOLDOWN_SWEEP_TTL_MS }
};