From 34dc55c20b57cdf78daa99a4154dfc217b68abcc Mon Sep 17 00:00:00 2001 From: indifferentketchup Date: Tue, 21 Apr 2026 16:44:01 +0000 Subject: [PATCH] strip: remove /backup /export /search /stats /fix-stale-tickets + analytics module - delete handlers/analytics.js - remove trackInteraction calls; replace trackError with logError().catch(() => {}) - remove 5 slash commands from register.js - remove BACKUP_EXPORT_CHANNEL_ID from config + schema + .env.example --- .env.example | 1 - commands/register.js | 72 ------------- config.js | 1 - game-options.json | 32 ------ handlers/analytics.js | 89 ---------------- handlers/buttons.js | 12 +-- handlers/commands.js | 213 ++------------------------------------- services/configSchema.js | 2 +- 8 files changed, 13 insertions(+), 409 deletions(-) delete mode 100644 game-options.json delete mode 100644 handlers/analytics.js diff --git a/.env.example b/.env.example index f4b50af..003e3f2 100644 --- a/.env.example +++ b/.env.example @@ -34,7 +34,6 @@ ROLE_ID_TO_PING= # Role ID to ping on new tickets (config also TRANSCRIPT_CHANNEL_ID= # Channel for ticket transcripts on close LOGGING_CHANNEL_ID= # Channel for lifecycle log messages DEBUGGING_CHANNEL_ID= # Channel for error logs (escalate, poll, etc.); optional -BACKUP_EXPORT_CHANNEL_ID= # Channel where /backup and /export post .txt files; optional DISCORD_CHANNEL_ID= # General Discord channel (if used) # --- Discord: Ticket copy & buttons --- diff --git a/commands/register.js b/commands/register.js index 70e6666..436b5cb 100644 --- a/commands/register.js +++ b/commands/register.js @@ -277,71 +277,6 @@ async function registerCommands() { ) ), - new SlashCommandBuilder() - .setName('backup') - .setDescription('Export full ticket list to a .txt file in the backup/export channel') - .setContexts([InteractionContextType.Guild]) - .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) - .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), - - new SlashCommandBuilder() - .setName('export') - .setDescription('Export tickets (optional filter and limit) to a .txt file in the backup/export channel') - .setContexts([InteractionContextType.Guild]) - .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) - .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) - .addStringOption(opt => - opt - .setName('status') - .setDescription('Filter by status') - .setRequired(false) - .addChoices( - { name: 'Open', value: 'open' }, - { name: 'Closed', value: 'closed' } - ) - ) - .addIntegerOption(opt => - opt - .setName('limit') - .setDescription('Max number of tickets to export (default 500)') - .setMinValue(1) - .setMaxValue(5000) - .setRequired(false) - ), - - new SlashCommandBuilder() - .setName('search') - .setDescription('Search for tickets') - .setContexts([InteractionContextType.Guild]) - .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) - .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages) - .addStringOption(opt => - opt - .setName('query') - .setDescription('Search query (email, subject, or ticket number)') - .setMinLength(2) - .setMaxLength(100) - .setRequired(true) - ) - .addStringOption(opt => - opt - .setName('status') - .setDescription('Filter by status') - .setRequired(false) - .addChoices( - { name: 'Open', value: 'open' }, - { name: 'Closed', value: 'closed' }, - { name: 'All', value: 'all' } - ) - ), - - new SlashCommandBuilder() - .setName('stats') - .setDescription('View bot statistics and analytics') - .setContexts([InteractionContextType.Guild]) - .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) - .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), - new SlashCommandBuilder() .setName('closetimer') .setDescription('Set the force-close countdown duration') @@ -456,13 +391,6 @@ async function registerCommands() { .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages), - new SlashCommandBuilder() - .setName('fix-stale-tickets') - .setDescription('Admin only: backfill lastActivity on open tickets where it is null (sets to createdAt).') - .setContexts([InteractionContextType.Guild]) - .setIntegrationTypes([ApplicationIntegrationType.GuildInstall]) - .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), - new SlashCommandBuilder() .setName('signature') .setDescription('Set your personal email signature (valediction, display name, tagline)') diff --git a/config.js b/config.js index ac5d50d..dca6b7c 100644 --- a/config.js +++ b/config.js @@ -47,7 +47,6 @@ const CONFIG = { TRANSCRIPT_CHAN: process.env.TRANSCRIPT_CHANNEL_ID, LOG_CHAN: process.env.LOGGING_CHANNEL_ID, DEBUGGING_CHANNEL_ID: process.env.DEBUGGING_CHANNEL_ID || null, - BACKUP_EXPORT_CHANNEL_ID: process.env.BACKUP_EXPORT_CHANNEL_ID || null, DISCORD_CHANNEL_ID: process.env.DISCORD_CHANNEL_ID || null, CLIENT_ID: process.env.DISCORD_APPLICATION_ID, REFRESH_TOKEN: process.env.REFRESH_TOKEN, diff --git a/game-options.json b/game-options.json deleted file mode 100644 index 5d76aa3..0000000 --- a/game-options.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "project_zomboid": "Project Zomboid", - "satisfactory": "Satisfactory", - "palworld": "Palworld", - "minecraft": "Minecraft", - "valheim": "Valheim", - "enshrouded": "Enshrouded", - "7_days_to_die": "7 Days to Die", - "hytale": "Hytale", - "icarus": "ICARUS", - "abiotic_factor": "Abiotic Factor", - "ark_survival_evolved": "ARK: Survival Evolved", - "conan_exiles": "Conan Exiles", - "core_keeper": "Core Keeper", - "counter_strike_2": "Counter-Strike 2", - "dayz": "DayZ", - "eco": "ECO", - "factorio": "Factorio", - "fivem": "FiveM", - "the_front": "The Front", - "garrys_mod": "Garry's Mod", - "necesse": "Necesse", - "rust": "Rust", - "sons_of_the_forest": "Sons of the Forest", - "soulmask": "Soulmask", - "star_rupture": "Star Rupture", - "terraria": "Terraria", - "vein": "VEIN", - "vintage_story": "Vintage Story", - "voyagers_of_nera": "Voyagers of Nera", - "v_rising": "V Rising" -} diff --git a/handlers/analytics.js b/handlers/analytics.js deleted file mode 100644 index a8f2ddb..0000000 --- a/handlers/analytics.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * In-memory analytics and error tracking. - */ -const { logError } = require('../services/debugLog'); - -const analytics = { - commands: {}, - buttons: {}, - modals: {}, - contextMenus: {}, - errors: [], - startTime: Date.now() -}; - -function trackInteraction(type, name, userId = 'unknown') { - analytics[type][name] = (analytics[type][name] || 0) + 1; - console.log(`📊 Analytics: ${type}/${name} by ${userId}`); -} - -function getTotalInteractions() { - let total = 0; - for (const type of ['commands', 'buttons', 'modals', 'contextMenus']) { - for (const key in analytics[type]) { - total += analytics[type][key]; - } - } - return total; -} - -function trackError(context, error, interaction = null) { - const errorEntry = { - context, - message: error.message, - stack: error.stack, - timestamp: Date.now(), - user: interaction?.user?.tag || 'system', - command: interaction?.commandName || 'N/A' - }; - - analytics.errors.push(errorEntry); - - if (analytics.errors.length > 100) { - analytics.errors.shift(); - } - - console.error(`❌ Error tracked: ${context}:`, error.message); - - logError(context, error, interaction); - - const recentErrors = analytics.errors.filter(e => - Date.now() - e.timestamp < 3600000 - ); - - const errorRate = recentErrors.length / Math.max(1, getTotalInteractions()); - - if (errorRate > 0.05) { - console.warn(`⚠️ HIGH ERROR RATE: ${(errorRate * 100).toFixed(2)}% in last hour`); - } -} - -function getAnalyticsSummary() { - const uptime = Math.floor((Date.now() - analytics.startTime) / 1000); - const totalInteractions = getTotalInteractions(); - const recentErrors = analytics.errors.filter(e => - Date.now() - e.timestamp < 3600000 - ); - - return { - uptime: `${Math.floor(uptime / 3600)}h ${Math.floor((uptime % 3600) / 60)}m`, - totalInteractions, - commandsUsed: Object.keys(analytics.commands).length, - mostUsedCommand: Object.entries(analytics.commands) - .sort((a, b) => b[1] - a[1])[0]?.[0] || 'None', - errorsLastHour: recentErrors.length, - errorRate: `${((recentErrors.length / Math.max(1, totalInteractions)) * 100).toFixed(2)}%`, - topCommands: Object.entries(analytics.commands) - .sort((a, b) => b[1] - a[1]) - .slice(0, 5) - .map(([cmd, count]) => `${cmd}: ${count}`) - }; -} - -module.exports = { - analytics, - trackInteraction, - trackError, - getTotalInteractions, - getAnalyticsSummary -}; diff --git a/handlers/buttons.js b/handlers/buttons.js index d969a60..7656f41 100644 --- a/handlers/buttons.js +++ b/handlers/buttons.js @@ -23,7 +23,6 @@ const { sanitizeEmbedText, truncateEmbedDescription, truncateEmbedField, enforce const { setEmailRouting } = require('../services/guildSettings'); const { enqueueRename, enqueueSend } = require('../services/channelQueue'); const { runEscalation, runDeescalation } = require('./commands'); -const { trackInteraction, trackError } = require('./analytics'); const { pendingCloses } = require('./pendingCloses'); const { logError, logSystem } = require('../services/debugLog'); @@ -90,7 +89,7 @@ async function handleButton(interaction) { ephemeral: true }); } catch (err) { - trackError('email-routing-button', err, interaction); + logError('email-routing-button', err, interaction).catch(() => {}); await interaction.reply({ content: 'Failed to update email routing.', ephemeral: true @@ -215,7 +214,7 @@ async function handleButton(interaction) { await interaction.deferReply(); await runEscalation(interaction, ticket, 1, null); } catch (err) { - trackError('escalate-button-tier2', err, interaction); + logError('escalate-button-tier2', err, interaction).catch(() => {}); await interaction.editReply({ content: 'Failed to escalate to tier 2.' }).catch(() => interaction.followUp({ content: 'Failed to escalate to tier 2.', ephemeral: true }).catch(() => {}) ); @@ -238,7 +237,7 @@ async function handleButton(interaction) { await interaction.deferReply(); await runEscalation(interaction, ticket, 2, null); } catch (err) { - trackError('escalate-button-tier3', err, interaction); + logError('escalate-button-tier3', err, interaction).catch(() => {}); await interaction.editReply({ content: 'Failed to escalate to tier 3.' }).catch(() => interaction.followUp({ content: 'Failed to escalate to tier 3.', ephemeral: true }).catch(() => {}) ); @@ -256,7 +255,7 @@ async function handleButton(interaction) { await interaction.deferReply({ ephemeral: true }); await runDeescalation(interaction, ticket); } catch (err) { - trackError('deescalate-button', err, interaction); + logError('deescalate-button', err, interaction).catch(() => {}); await interaction.editReply({ content: 'Failed to deescalate this ticket.' }).catch(() => interaction.followUp({ content: 'Failed to deescalate this ticket.', ephemeral: true }).catch(() => {}) ); @@ -266,7 +265,6 @@ async function handleButton(interaction) { // --- TAG DELETE CONFIRM --- if (interaction.customId.startsWith('confirm_delete_tag::')) { - trackInteraction('buttons', 'confirm-delete-tag', interaction.user.tag); const tagName = interaction.customId.slice('confirm_delete_tag::'.length); try { @@ -284,7 +282,7 @@ async function handleButton(interaction) { }); } } catch (err) { - trackError('tag-delete-confirm', err, interaction); + logError('tag-delete-confirm', err, interaction).catch(() => {}); await interaction.update({ content: '❌ Failed to delete tag.', components: [] diff --git a/handlers/commands.js b/handlers/commands.js index fd18eee..282805a 100644 --- a/handlers/commands.js +++ b/handlers/commands.js @@ -12,14 +12,13 @@ const { } = require('discord.js'); const { mongoose } = require('../db-connection'); const { CONFIG } = require('../config'); -const { getPriorityEmoji, replaceVariables, escapeRegex } = require('../utils'); +const { getPriorityEmoji, replaceVariables } = require('../utils'); const { makeTicketName, resolveCreatorNickname, getOrCreateTicketCategory, createDiscordTicketAsThread, checkTicketCreationRateLimit } = require('../services/tickets'); const { sendTicketNotificationEmail } = require('../services/gmail'); const { getTicketActionRow } = require('../utils/ticketComponents'); const { getEmailRouting } = require('../services/guildSettings'); const { enqueueRename, enqueueMove, enqueueSend } = require('../services/channelQueue'); const { setNotifyDm } = require('../services/staffSettings'); -const { trackInteraction, trackError, getAnalyticsSummary } = require('./analytics'); const { logTicketEvent, logSecurity, logError } = require('../services/debugLog'); const { handleSetupCommand } = require('./setup'); const { pendingCloses } = require('./pendingCloses'); @@ -249,7 +248,7 @@ async function handleCommand(interaction) { components: [row] }); } catch (err) { - trackError('email-routing-command', err, interaction); + logError('email-routing-command', err, interaction).catch(() => {}); await interaction.editReply('Failed to load routing options.').catch(() => {}); } return; @@ -631,7 +630,6 @@ async function handleCommand(interaction) { // /response – saved response tags (send, create, edit, delete, list) if (interaction.commandName === 'response') { - trackInteraction('commands', 'response', interaction.user.tag); const subcommand = interaction.options.getSubcommand(); try { @@ -671,7 +669,7 @@ async function handleCommand(interaction) { if (err.code === 11000 || err.message?.includes('duplicate')) { await interaction.reply({ content: `❌ Tag "${name}" already exists.`, ephemeral: true }); } else { - trackError('tag-create', err, interaction); + logError('tag-create', err, interaction).catch(() => {}); await interaction.reply({ content: '❌ Failed to create tag.', ephemeral: true }); } } @@ -690,7 +688,7 @@ async function handleCommand(interaction) { await interaction.reply({ content: `✅ Tag "${name}" updated successfully.`, ephemeral: true }); } } catch (err) { - trackError('tag-edit', err, interaction); + logError('tag-edit', err, interaction).catch(() => {}); await interaction.reply({ content: '❌ Failed to edit tag.', ephemeral: true }); } } @@ -737,7 +735,7 @@ async function handleCommand(interaction) { await interaction.editReply({ embeds: [embed] }); } } catch (err) { - trackError('response-command', err, interaction); + logError('response-command', err, interaction).catch(() => {}); const errorMsg = '❌ An error occurred while processing the response command.'; if (interaction.deferred) { await interaction.editReply(errorMsg); @@ -898,201 +896,6 @@ async function handleCommand(interaction) { } } - // /backup – export full ticket list to BACKUP_EXPORT_CHANNEL_ID - if (interaction.commandName === 'backup') { - trackInteraction('commands', 'backup', interaction.user.tag); - await interaction.deferReply({ ephemeral: true }); - if (!CONFIG.BACKUP_EXPORT_CHANNEL_ID) { - return interaction.editReply('BACKUP_EXPORT_CHANNEL_ID is not set in .env.'); - } - try { - // Stream every ticket through a Mongoose cursor to a tmp file so peak RSS - // stays bounded regardless of collection size; attach the file, then unlink. - const fs = require('fs'); - const os = require('os'); - const path = require('path'); - const tmpName = `ticket-backup-${Date.now()}-${process.pid}.txt`; - const tmpPath = path.join(os.tmpdir(), tmpName); - const ws = fs.createWriteStream(tmpPath, { encoding: 'utf8' }); - - ws.write('# Ticket backup – ' + new Date().toISOString() + '\n'); - ws.write('ticketNumber\tstatus\tsenderEmail\tsubject\tcreatedAt\tclaimedBy\tpriority\tescalationTier\n'); - - let count = 0; - const cursor = Ticket.find().sort({ ticketNumber: 1 }).lean().cursor(); - for await (const t of cursor) { - const created = t.createdAt ? new Date(t.createdAt).toISOString() : ''; - ws.write([ - t.ticketNumber, - t.status || '', - (t.senderEmail || '').replace(/\t/g, ' '), - (t.subject || '').replace(/\t/g, ' ').slice(0, 200), - created, - (t.claimedBy || '').replace(/\t/g, ' '), - t.priority || '', - t.escalationTier ?? '' - ].join('\t') + '\n'); - count++; - } - await new Promise((resolve, reject) => ws.end(err => err ? reject(err) : resolve())); - - try { - const channel = await interaction.client.channels.fetch(CONFIG.BACKUP_EXPORT_CHANNEL_ID); - await enqueueSend(channel, { - content: `Ticket backup by ${interaction.user.tag} (${count} tickets)`, - files: [new AttachmentBuilder(tmpPath, { name: tmpName })] - }); - await interaction.editReply(`Backup complete. ${count} tickets sent to the backup channel.`); - } finally { - fs.promises.unlink(tmpPath).catch(() => {}); - } - } catch (err) { - trackError('backup-command', err, interaction); - await interaction.editReply('Failed to create backup: ' + (err.message || err)); - } - } - - // /export – export tickets with optional status and limit to BACKUP_EXPORT_CHANNEL_ID - if (interaction.commandName === 'export') { - trackInteraction('commands', 'export', interaction.user.tag); - await interaction.deferReply({ ephemeral: true }); - if (!CONFIG.BACKUP_EXPORT_CHANNEL_ID) { - return interaction.editReply('BACKUP_EXPORT_CHANNEL_ID is not set in .env.'); - } - try { - const status = interaction.options.getString('status') || null; - const limit = interaction.options.getInteger('limit') || 500; - const filter = status ? { status } : {}; - const tickets = await Ticket.find(filter).sort({ ticketNumber: -1 }).limit(limit).lean(); - const lines = ['# Ticket export – ' + new Date().toISOString() + (status ? ` (status=${status})` : '') + ` limit=${limit}`, 'ticketNumber\tstatus\tsenderEmail\tsubject\tcreatedAt\tclaimedBy\tpriority\tescalationTier']; - for (const t of tickets) { - const created = t.createdAt ? new Date(t.createdAt).toISOString() : ''; - lines.push([t.ticketNumber, t.status || '', (t.senderEmail || '').replace(/\t/g, ' '), (t.subject || '').replace(/\t/g, ' ').slice(0, 200), created, (t.claimedBy || '').replace(/\t/g, ' '), t.priority || '', t.escalationTier ?? ''].join('\t')); - } - const buf = Buffer.from(lines.join('\n'), 'utf8'); - const channel = await interaction.client.channels.fetch(CONFIG.BACKUP_EXPORT_CHANNEL_ID); - await enqueueSend(channel, { - content: `Ticket export by ${interaction.user.tag} (${tickets.length} tickets${status ? ` status=${status}` : ''})`, - files: [new AttachmentBuilder(buf, { name: `ticket-export-${Date.now()}.txt` })] - }); - await interaction.editReply(`Export complete. ${tickets.length} tickets sent to the backup channel.`); - } catch (err) { - trackError('export-command', err, interaction); - await interaction.editReply('Failed to export: ' + (err.message || err)); - } - } - - // /search - if (interaction.commandName === 'search') { - trackInteraction('commands', 'search', interaction.user.tag); - await interaction.deferReply({ ephemeral: true }); - - try { - const query = interaction.options.getString('query'); - const status = interaction.options.getString('status') || 'all'; - - const regex = new RegExp(escapeRegex(query), 'i'); - const filter = { - $or: [ - { senderEmail: regex }, - { subject: regex } - ] - }; - const ticketNum = parseInt(query, 10); - if (!Number.isNaN(ticketNum) && String(ticketNum) === query.trim()) { - filter.$or.push({ ticketNumber: ticketNum }); - } - if (status !== 'all') filter.status = status; - - const results = await Ticket.find(filter).sort({ createdAt: -1 }).limit(10).lean(); - - if (!results || results.length === 0) { - return interaction.editReply('🔍 No tickets found matching your query.'); - } - - const embed = new EmbedBuilder() - .setTitle(`🔍 Search Results for "${query}"`) - .setDescription(`Found ${results.length} ticket(s)`) - .setColor(CONFIG.EMBED_COLOR_INFO); - - for (const ticket of results.slice(0, 5)) { - const priorityEmoji = getPriorityEmoji(ticket.priority || 'normal'); - const statusEmoji = ticket.status === 'open' ? '🟢' : '🔴'; - embed.addFields({ - name: `${priorityEmoji} Ticket #${ticket.ticketNumber} ${statusEmoji}`, - value: `**Subject:** ${ticket.subject || 'No subject'}\n**From:** ${ticket.senderEmail}\n**Status:** ${ticket.status}\n**Claimed:** ${ticket.claimedBy || 'Unclaimed'}`, - inline: false - }); - } - - if (results.length > 5) { - embed.setFooter({ text: `Showing 5 of ${results.length} results` }); - } - - await interaction.editReply({ embeds: [embed] }); - } catch (err) { - trackError('search-command', err, interaction); - await interaction.editReply('❌ An error occurred while searching.'); - } - } - - // /fix-stale-tickets - if (interaction.commandName === 'fix-stale-tickets') { - if (interaction.user.id !== CONFIG.ADMIN_ID) { - return interaction.reply({ content: 'You do not have permission to run this command.', ephemeral: true }); - } - await interaction.deferReply({ ephemeral: true }); - try { - const result = await Ticket.updateMany( - { status: 'open', lastActivity: null }, - [{ $set: { lastActivity: '$createdAt' } }] - ); - await interaction.editReply(`Fixed ${result.modifiedCount} ticket(s).`); - } catch (err) { - console.error('fix-stale-tickets:', err); - await interaction.editReply('❌ Failed to backfill tickets.').catch(() => {}); - } - } - - // /stats - if (interaction.commandName === 'stats') { - trackInteraction('commands', 'stats', interaction.user.tag); - await interaction.deferReply({ ephemeral: true }); - - try { - const summary = getAnalyticsSummary(); - - const ticketStats = await Ticket.aggregate([ - { $group: { _id: '$status', count: { $sum: 1 } } } - ]); - - const openCount = ticketStats.find(s => s._id === 'open')?.count || 0; - const closedCount = ticketStats.find(s => s._id === 'closed')?.count || 0; - const claimedCount = await Ticket.countDocuments({ status: 'open', claimedBy: { $ne: null } }); - - const embed = new EmbedBuilder() - .setTitle('📊 Bot Statistics & Analytics') - .setColor(CONFIG.EMBED_COLOR_INFO) - .addFields([ - { name: '⏱️ Uptime', value: summary.uptime, inline: true }, - { name: '💬 Total Interactions', value: summary.totalInteractions.toString(), inline: true }, - { name: '📈 Commands Used', value: summary.commandsUsed.toString(), inline: true }, - { name: '🎫 Open Tickets', value: openCount.toString(), inline: true }, - { name: '✅ Closed Tickets', value: closedCount.toString(), inline: true }, - { name: '📌 Claimed Tickets', value: (claimedCount || 0).toString(), inline: true }, - { name: '🔥 Most Used Command', value: summary.mostUsedCommand, inline: true }, - { name: '❌ Errors (Last Hour)', value: summary.errorsLastHour.toString(), inline: true }, - { name: '📉 Error Rate', value: summary.errorRate, inline: true }, - { name: '📋 Top Commands', value: summary.topCommands.join('\n') || 'None', inline: false } - ]) - .setTimestamp(); - - await interaction.editReply({ embeds: [embed] }); - } catch (err) { - trackError('stats-command', err, interaction); - await interaction.editReply('❌ An error occurred while fetching statistics.'); - } - } } /** @@ -1104,7 +907,6 @@ async function handleContextMenu(interaction) { // Create Ticket From Message if (interaction.isMessageContextMenuCommand() && interaction.commandName === 'Create Ticket From Message') { - trackInteraction('contextMenus', 'create-ticket-from-message', interaction.user.tag); await interaction.deferReply({ ephemeral: true }); const rateLimit = checkTicketCreationRateLimit(interaction.user.id); @@ -1215,14 +1017,13 @@ async function handleContextMenu(interaction) { await interaction.editReply(`✅ Ticket created: ${channel}`); } catch (err) { - trackError('create-ticket-from-message', err, interaction); + logError('create-ticket-from-message', err, interaction).catch(() => {}); await interaction.editReply('❌ Failed to create ticket from message.'); } } // View User Tickets if (interaction.isUserContextMenuCommand() && interaction.commandName === 'View User Tickets') { - trackInteraction('contextMenus', 'view-user-tickets', interaction.user.tag); await interaction.deferReply({ ephemeral: true }); try { @@ -1258,7 +1059,7 @@ async function handleContextMenu(interaction) { await interaction.editReply({ embeds: [embed] }); } catch (err) { - trackError('view-user-tickets', err, interaction); + logError('view-user-tickets', err, interaction).catch(() => {}); await interaction.editReply('❌ Failed to fetch user tickets.'); } } diff --git a/services/configSchema.js b/services/configSchema.js index c4b27e1..70cce77 100644 --- a/services/configSchema.js +++ b/services/configSchema.js @@ -30,7 +30,7 @@ const ALLOWED_CONFIG_KEYS = new Set([ 'ADMIN_ID', // Channel IDs 'TRANSCRIPT_CHANNEL_ID', 'LOGGING_CHANNEL_ID', 'DEBUGGING_CHANNEL_ID', - 'BACKUP_EXPORT_CHANNEL_ID', 'DISCORD_CHANNEL_ID', + 'DISCORD_CHANNEL_ID', 'GMAIL_LOG_CHANNEL_ID', 'AUTOMATION_LOG_CHANNEL_ID', 'RENAME_LOG_CHANNEL_ID', 'SECURITY_LOG_CHANNEL_ID', 'SYSTEM_LOG_CHANNEL_ID', // Messages and labels