diff --git a/broccolini-discord.js b/broccolini-discord.js index 61f986f..f57161a 100644 --- a/broccolini-discord.js +++ b/broccolini-discord.js @@ -16,7 +16,7 @@ const { handleDiscordReply } = require('./handlers/messages'); // Services & jobs const { sendTicketClosedEmail } = require('./services/gmail'); -const { checkAutoClose, checkAutoUnclaim } = require('./services/tickets'); +const { checkAutoClose, checkAutoUnclaim, reconcileDeletedTicketChannels } = require('./services/tickets'); const { notifyAllStaffUnclaimed } = require('./services/staffNotifications'); const { registerCommands } = require('./commands/register'); const bosscordRoutes = require('./routes/bosscord'); @@ -203,6 +203,10 @@ client.once('ready', async () => { setInterval(() => runChatAlertChecks(client).catch(e => console.error('runChatAlertChecks:', e)), 5 * 60 * 1000); console.log('✓ Chat alert monitoring: every 5 minutes'); + reconcileDeletedTicketChannels(client).catch(e => console.error('reconcileDeletedTicketChannels:', e)); + setInterval(() => reconcileDeletedTicketChannels(client).catch(e => console.error('reconcileDeletedTicketChannels:', e)), 60 * 60 * 1000); + console.log('✓ Reconcile deleted ticket channels: every 1 hour'); + if (!CONFIG.STAFF_IDS.length) { console.warn('[surgeChecker] STAFF_IDS is not set — zero-staff detection disabled.'); } diff --git a/services/surgeChecker.js b/services/surgeChecker.js index 64ff55c..37fecd4 100644 --- a/services/surgeChecker.js +++ b/services/surgeChecker.js @@ -105,7 +105,7 @@ async function checkUnclaimedSurge(client) { const count = await Ticket.countDocuments({ status: 'open', claimedBy: null, - createdAt: { $lte: cutoff } + createdAt: { $lte: cutoff, $ne: null } }); if (count >= CONFIG.SURGE_UNCLAIMED_COUNT) { setCooldown('surge:unclaimed'); @@ -123,7 +123,7 @@ async function checkTier3UnclaimedSurge(client) { status: 'open', escalationTier: 2, claimedBy: null, - createdAt: { $lte: cutoff } + createdAt: { $lte: cutoff, $ne: null } }).lean(); if (tickets.length > 0) { setCooldown('surge:tier3_unclaimed'); diff --git a/services/tickets.js b/services/tickets.js index af43cc3..7a587d4 100644 --- a/services/tickets.js +++ b/services/tickets.js @@ -584,6 +584,39 @@ async function checkAutoUnclaim(client) { logAutomation('Auto-unclaim run', null, `checked: ${checked}, unclaimed: ${unclaimed}`).catch(() => {}); } +async function reconcileDeletedTicketChannels(client) { + const guild = client.guilds.cache.get(CONFIG.DISCORD_GUILD_ID) || client.guilds.cache.first(); + if (!guild) return { checked: 0, reconciled: 0 }; + + const openTickets = await Ticket.find({ + status: 'open', + discordThreadId: { $ne: null } + }).lean(); + + let checked = 0, reconciled = 0; + for (const ticket of openTickets) { + checked++; + try { + let channel = guild.channels.cache.get(ticket.discordThreadId); + if (!channel) { + channel = await guild.channels.fetch(ticket.discordThreadId).catch(() => null); + } + if (!channel) { + await Ticket.updateOne( + { gmailThreadId: ticket.gmailThreadId }, + { $set: { status: 'closed', discordThreadId: null } } + ); + logAutomation('Reconcile: channel deleted', ticket.discordThreadId, `ticket #${ticket.ticketNumber}`).catch(() => {}); + reconciled++; + } + } catch (err) { + console.error(`reconcileDeletedTicketChannels error for ${ticket.gmailThreadId}:`, err); + } + } + logAutomation('Reconcile run', null, `checked: ${checked}, reconciled: ${reconciled}`).catch(() => {}); + return { checked, reconciled }; +} + module.exports = { getNextTicketNumber, pickTicketCategoryId, @@ -606,5 +639,6 @@ module.exports = { updateTicketActivity, checkAutoClose, checkReminders, - checkAutoUnclaim + checkAutoUnclaim, + reconcileDeletedTicketChannels };