notification changes
This commit is contained in:
@@ -3,13 +3,21 @@
|
||||
* and pings ALL_STAFF_CHANNEL_ID with role mention.
|
||||
*/
|
||||
const { EmbedBuilder } = require('discord.js');
|
||||
const { CONFIG } = require('../config');
|
||||
const { CONFIG, parseThresholdString } = require('../config');
|
||||
const { mongoose } = require('../db-connection');
|
||||
const { setCooldown, isOnCooldown, isStaffRecentlyActive } = require('./patternStore');
|
||||
const { shouldFireCooldownEscalating, clearEscalating, isStaffRecentlyActive } = require('./patternStore');
|
||||
const { getStaffAvailability, isAnyStaffAvailable } = require('./staffPresence');
|
||||
|
||||
const Ticket = mongoose.model('Ticket');
|
||||
|
||||
function getThresholdsMs(alertKey) {
|
||||
const rawThresholds = (CONFIG.NOTIFICATION_THRESHOLDS && CONFIG.NOTIFICATION_THRESHOLDS[alertKey]) || [];
|
||||
return rawThresholds
|
||||
.map(parseThresholdString)
|
||||
.filter(n => Number.isFinite(n) && n >= 0)
|
||||
.sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
async function pingStaff(client, message, embedFields) {
|
||||
const channelId = CONFIG.ALL_STAFF_CHANNEL_ID;
|
||||
if (!channelId || !client) return;
|
||||
@@ -34,20 +42,24 @@ async function pingStaff(client, message, embedFields) {
|
||||
}
|
||||
|
||||
async function checkTicketSurge(client) {
|
||||
if (isOnCooldown('surge:tickets', CONFIG.SURGE_COOLDOWN_MINUTES)) return;
|
||||
const key = 'surge:tickets';
|
||||
const since = new Date(Date.now() - CONFIG.SURGE_TICKET_WINDOW_MINUTES * 60000);
|
||||
const count = await Ticket.countDocuments({ createdAt: { $gte: since } });
|
||||
if (count >= CONFIG.SURGE_TICKET_COUNT) {
|
||||
setCooldown('surge:tickets');
|
||||
await pingStaff(client,
|
||||
`${count} tickets created in the past ${CONFIG.SURGE_TICKET_WINDOW_MINUTES} minutes.`,
|
||||
[{ name: 'Action needed', value: 'Check open tickets and claim.', inline: false }]
|
||||
);
|
||||
const thresholdMs = shouldFireCooldownEscalating(key, getThresholdsMs('surge_tickets'));
|
||||
if (thresholdMs !== null) {
|
||||
await pingStaff(client,
|
||||
`${count} tickets created in the past ${CONFIG.SURGE_TICKET_WINDOW_MINUTES} minutes.`,
|
||||
[{ name: 'Action needed', value: 'Check open tickets and claim.', inline: false }]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
clearEscalating(key);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkGameSurge(client) {
|
||||
if (isOnCooldown('surge:game', CONFIG.SURGE_COOLDOWN_MINUTES)) return;
|
||||
const key = 'surge:game';
|
||||
const since = new Date(Date.now() - CONFIG.SURGE_GAME_TICKET_WINDOW_MINUTES * 60000);
|
||||
const gameCounts = await Ticket.aggregate([
|
||||
{ $match: { createdAt: { $gte: since }, game: { $ne: null } } },
|
||||
@@ -56,34 +68,42 @@ async function checkGameSurge(client) {
|
||||
{ $sort: { count: -1 } }
|
||||
]);
|
||||
if (gameCounts.length > 0) {
|
||||
setCooldown('surge:game');
|
||||
const fields = gameCounts.map(g => ({
|
||||
name: g._id,
|
||||
value: `${g.count} tickets in ${CONFIG.SURGE_GAME_TICKET_WINDOW_MINUTES} min`,
|
||||
inline: true
|
||||
}));
|
||||
await pingStaff(client, 'Game ticket surge detected.', fields);
|
||||
const thresholdMs = shouldFireCooldownEscalating(key, getThresholdsMs('surge_game'));
|
||||
if (thresholdMs !== null) {
|
||||
const fields = gameCounts.map(g => ({
|
||||
name: g._id,
|
||||
value: `${g.count} tickets in ${CONFIG.SURGE_GAME_TICKET_WINDOW_MINUTES} min`,
|
||||
inline: true
|
||||
}));
|
||||
await pingStaff(client, 'Game ticket surge detected.', fields);
|
||||
}
|
||||
} else {
|
||||
clearEscalating(key);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkStaleSurge(client) {
|
||||
if (isOnCooldown('surge:stale', CONFIG.SURGE_COOLDOWN_MINUTES)) return;
|
||||
const key = 'surge:stale';
|
||||
const cutoff = new Date(Date.now() - CONFIG.SURGE_STALE_HOURS * 3600000);
|
||||
const count = await Ticket.countDocuments({
|
||||
status: 'open',
|
||||
lastActivity: { $lte: cutoff, $ne: null }
|
||||
});
|
||||
if (count >= CONFIG.SURGE_STALE_COUNT) {
|
||||
setCooldown('surge:stale');
|
||||
await pingStaff(client,
|
||||
`${count} tickets have had no activity in the past ${CONFIG.SURGE_STALE_HOURS} hours.`,
|
||||
[{ name: 'Action needed', value: 'Review and respond to stale tickets.', inline: false }]
|
||||
);
|
||||
const thresholdMs = shouldFireCooldownEscalating(key, getThresholdsMs('surge_stale'));
|
||||
if (thresholdMs !== null) {
|
||||
await pingStaff(client,
|
||||
`${count} tickets have had no activity in the past ${CONFIG.SURGE_STALE_HOURS} hours.`,
|
||||
[{ name: 'Action needed', value: 'Review and respond to stale tickets.', inline: false }]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
clearEscalating(key);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkNeedsResponseSurge(client) {
|
||||
if (isOnCooldown('surge:needs_response', CONFIG.SURGE_COOLDOWN_MINUTES)) return;
|
||||
const key = 'surge:needs_response';
|
||||
const cutoff = new Date(Date.now() - CONFIG.SURGE_NEEDS_RESPONSE_HOURS * 3600000);
|
||||
const count = await Ticket.countDocuments({
|
||||
status: 'open',
|
||||
@@ -91,16 +111,20 @@ async function checkNeedsResponseSurge(client) {
|
||||
lastActivity: { $lte: cutoff, $ne: null }
|
||||
});
|
||||
if (count >= CONFIG.SURGE_NEEDS_RESPONSE_COUNT) {
|
||||
setCooldown('surge:needs_response');
|
||||
await pingStaff(client,
|
||||
`${count} tickets are waiting on a staff response for over ${CONFIG.SURGE_NEEDS_RESPONSE_HOURS} hour(s).`,
|
||||
[]
|
||||
);
|
||||
const thresholdMs = shouldFireCooldownEscalating(key, getThresholdsMs('surge_needs_response'));
|
||||
if (thresholdMs !== null) {
|
||||
await pingStaff(client,
|
||||
`${count} tickets are waiting on a staff response for over ${CONFIG.SURGE_NEEDS_RESPONSE_HOURS} hour(s).`,
|
||||
[]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
clearEscalating(key);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkUnclaimedSurge(client) {
|
||||
if (isOnCooldown('surge:unclaimed', CONFIG.SURGE_COOLDOWN_MINUTES)) return;
|
||||
const key = 'surge:unclaimed';
|
||||
const cutoff = new Date(Date.now() - CONFIG.SURGE_UNCLAIMED_MINUTES * 60000);
|
||||
const count = await Ticket.countDocuments({
|
||||
status: 'open',
|
||||
@@ -108,16 +132,20 @@ async function checkUnclaimedSurge(client) {
|
||||
createdAt: { $lte: cutoff, $ne: null }
|
||||
});
|
||||
if (count >= CONFIG.SURGE_UNCLAIMED_COUNT) {
|
||||
setCooldown('surge:unclaimed');
|
||||
await pingStaff(client,
|
||||
`${count} tickets have been unclaimed for over ${CONFIG.SURGE_UNCLAIMED_MINUTES} minutes.`,
|
||||
[]
|
||||
);
|
||||
const thresholdMs = shouldFireCooldownEscalating(key, getThresholdsMs('surge_unclaimed'));
|
||||
if (thresholdMs !== null) {
|
||||
await pingStaff(client,
|
||||
`${count} tickets have been unclaimed for over ${CONFIG.SURGE_UNCLAIMED_MINUTES} minutes.`,
|
||||
[]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
clearEscalating(key);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkTier3UnclaimedSurge(client) {
|
||||
if (isOnCooldown('surge:tier3_unclaimed', 30)) return;
|
||||
const key = 'surge:tier3_unclaimed';
|
||||
const cutoff = new Date(Date.now() - CONFIG.SURGE_TIER3_UNCLAIMED_MINUTES * 60000);
|
||||
const tickets = await Ticket.find({
|
||||
status: 'open',
|
||||
@@ -126,23 +154,36 @@ async function checkTier3UnclaimedSurge(client) {
|
||||
createdAt: { $lte: cutoff, $ne: null }
|
||||
}).lean();
|
||||
if (tickets.length > 0) {
|
||||
setCooldown('surge:tier3_unclaimed');
|
||||
await pingStaff(client,
|
||||
`${tickets.length} Tier 3 ticket(s) unclaimed for over ${CONFIG.SURGE_TIER3_UNCLAIMED_MINUTES} minutes.`,
|
||||
tickets.map(t => ({ name: t.subject || 'No subject', value: `<#${t.discordThreadId}>`, inline: true }))
|
||||
);
|
||||
const thresholdMs = shouldFireCooldownEscalating(key, getThresholdsMs('surge_tier3_unclaimed'));
|
||||
if (thresholdMs !== null) {
|
||||
await pingStaff(client,
|
||||
`${tickets.length} Tier 3 ticket(s) unclaimed for over ${CONFIG.SURGE_TIER3_UNCLAIMED_MINUTES} minutes.`,
|
||||
tickets.map(t => ({ name: t.subject || 'No subject', value: `<#${t.discordThreadId}>`, inline: true }))
|
||||
);
|
||||
}
|
||||
} else {
|
||||
clearEscalating(key);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkZeroStaffSurge(client) {
|
||||
if (isOnCooldown('surge:no_staff', CONFIG.SURGE_NO_STAFF_COOLDOWN_MINUTES)) return;
|
||||
if (!CONFIG.STAFF_IDS.length) return;
|
||||
const key = 'surge:no_staff';
|
||||
if (!CONFIG.STAFF_IDS.length) {
|
||||
clearEscalating(key);
|
||||
return;
|
||||
}
|
||||
|
||||
const openCount = await Ticket.countDocuments({ status: 'open' });
|
||||
if (openCount < CONFIG.SURGE_NO_STAFF_OPEN_TICKET_THRESHOLD) return;
|
||||
if (openCount < CONFIG.SURGE_NO_STAFF_OPEN_TICKET_THRESHOLD) {
|
||||
clearEscalating(key);
|
||||
return;
|
||||
}
|
||||
|
||||
const guild = client.guilds.cache.get(CONFIG.DISCORD_GUILD_ID);
|
||||
if (!guild) return;
|
||||
if (!guild) {
|
||||
clearEscalating(key);
|
||||
return;
|
||||
}
|
||||
|
||||
const { available, source } = isAnyStaffAvailable(guild);
|
||||
|
||||
@@ -162,9 +203,13 @@ async function checkZeroStaffSurge(client) {
|
||||
detailLine = `${offline.length} staff offline/invisible${dndNote}. ${online.length} online.`;
|
||||
}
|
||||
|
||||
if (!noStaff) return;
|
||||
if (!noStaff) {
|
||||
clearEscalating(key);
|
||||
return;
|
||||
}
|
||||
|
||||
setCooldown('surge:no_staff');
|
||||
const thresholdMs = shouldFireCooldownEscalating(key, getThresholdsMs('surge_no_staff'));
|
||||
if (thresholdMs === null) return;
|
||||
|
||||
const fields = [
|
||||
{ name: 'Open tickets', value: String(openCount), inline: true },
|
||||
|
||||
Reference in New Issue
Block a user