/** * Broccolini Bot configuration and game lists. * * Never commit .env; agents must not modify .env without explicit user confirmation. */ require('dotenv').config({ debug: process.env.NODE_ENV === 'development' }); function toInt(v, fallback) { const n = parseInt(v, 10); return Number.isFinite(n) ? n : fallback; } const CONFIG = { DISCORD_TOKEN: (process.env.DISCORD_TOKEN || process.env.DISCORD_BOT_TOKEN || '').trim(), DISCORD_GUILD_ID: process.env.DISCORD_GUILD_ID || null, TICKET_CATEGORY_ID: process.env.TICKET_CATEGORY_ID, TICKET_CATEGORY_NAME: process.env.TICKET_CATEGORY_NAME || 'Open Tickets', DISCORD_TICKET_CATEGORY_ID: process.env.DISCORD_TICKET_CATEGORY_ID || process.env.TICKET_CATEGORY_ID, ROLE_ID_TO_PING: process.env.ROLE_ID_TO_PING, ROLE_TO_PING_ID: process.env.ROLE_ID_TO_PING || process.env.ROLE_TO_PING_ID, TRANSCRIPT_CHANNEL_ID: process.env.TRANSCRIPT_CHANNEL_ID, LOGGING_CHANNEL_ID: process.env.LOGGING_CHANNEL_ID, DEBUGGING_CHANNEL_ID: process.env.DEBUGGING_CHANNEL_ID || null, CLIENT_ID: process.env.DISCORD_APPLICATION_ID, REFRESH_TOKEN: process.env.REFRESH_TOKEN, MY_EMAIL: (process.env.MY_EMAIL || '').toLowerCase(), LOGO_URL: process.env.LOGO_URL, SUPPORT_NAME: process.env.SUPPORT_NAME || 'Support', STAFF_EMOJIS: Object.fromEntries((process.env.STAFF_EMOJIS||'').split(',').map(s=>s.trim()).filter(Boolean).map(p=>{const i=p.indexOf(':');return i===-1?null:[p.slice(0,i).trim(),p.slice(i+1).trim()];}).filter(Boolean)), CLAIMER_EMOJI_FALLBACK: process.env.CLAIMER_EMOJI_FALLBACK || '🎫', PORT: toInt(process.env.DISCORD_ONLY_PORT, 5000), HEALTHCHECK_HOST: process.env.HEALTHCHECK_HOST || null, // null = listen on all interfaces; set to 127.0.0.1 for local-only SIGNATURE: (process.env.EMAIL_SIGNATURE || '').trim().replace(/\\n/g, '\n'), GAME_LIST: process.env.GAME_LIST || '', // Tier 2/3 escalation: category IDs where ticket channels are placed (env uses *_CHANNEL_* for legacy naming). EMAIL_ESCALATED2_CHANNEL_ID: process.env.EMAIL_ESCALATED2_CHANNEL_ID || null, DISCORD_ESCALATED2_CHANNEL_ID: process.env.DISCORD_ESCALATED2_CHANNEL_ID || null, EMAIL_ESCALATED3_CHANNEL_ID: process.env.EMAIL_ESCALATED3_CHANNEL_ID || null, DISCORD_ESCALATED3_CHANNEL_ID: process.env.DISCORD_ESCALATED3_CHANNEL_ID || null, ESCALATION_MESSAGE: process.env.ESCALATION_MESSAGE || 'Your ticket has been escalated.\n\nA senior {support_name} will be here to assist as soon as possible.', TICKET_CLOSE_SUBJECT_PREFIX: process.env.TICKET_CLOSE_SUBJECT_PREFIX || '[Resolved]', // Email tickets only (closure email body): TICKET_CLOSE_MESSAGE: process.env.TICKET_CLOSE_MESSAGE || 'This ticket has been marked as resolved. If you would like to re-open this issue, please reply to this email.', TICKET_CLOSE_SIGNATURE: process.env.TICKET_CLOSE_SIGNATURE || 'Thank you for using Indifferent Broccoli.', // Discord ticket closure (in-channel and transcript): DISCORD_CLOSE_MESSAGE: process.env.DISCORD_CLOSE_MESSAGE || 'This ticket has been closed. A transcript has been saved. If you still need assistance, please open a new ticket.', DISCORD_TRANSCRIPT_MESSAGE: process.env.DISCORD_TRANSCRIPT_MESSAGE || 'Your ticket **{channel_name}** has been closed. Here is your transcript. If you still need assistance, please open a new ticket.', DISCORD_AUTO_CLOSE_MESSAGE: process.env.DISCORD_AUTO_CLOSE_MESSAGE || 'This ticket was closed due to inactivity. If you still need assistance, please open a new ticket.', AUTO_CLOSE_ENABLED: process.env.AUTO_CLOSE_ENABLED === 'true', AUTO_CLOSE_AFTER_HOURS: toInt(process.env.AUTO_CLOSE_AFTER_HOURS, 72), GLOBAL_TICKET_LIMIT: toInt(process.env.GLOBAL_TICKET_LIMIT, 5), RATE_LIMIT_TICKETS_PER_USER: toInt(process.env.RATE_LIMIT_TICKETS_PER_USER, 0), RATE_LIMIT_WINDOW_MINUTES: toInt(process.env.RATE_LIMIT_WINDOW_MINUTES, 60), BLACKLISTED_ROLES: (process.env.BLACKLISTED_ROLES || '').split(',').map(r => r.trim()).filter(Boolean), ADDITIONAL_STAFF_ROLES: (process.env.ADDITIONAL_STAFF_ROLES || '').split(',').map(r => r.trim()).filter(Boolean), TICKET_WELCOME_MESSAGE: process.env.TICKET_WELCOME_MESSAGE || "We got your ticket. We'll be with you as soon as possible. Feel free to add any additional information to your ticket.", TICKET_CLAIMED_MESSAGE: process.env.TICKET_CLAIMED_MESSAGE || 'Ticket claimed by {staff_mention} 🚀', TICKET_UNCLAIMED_MESSAGE: process.env.TICKET_UNCLAIMED_MESSAGE || 'Ticket unclaimed by {staff_mention} ☀️', REMINDER_ENABLED: process.env.REMINDER_ENABLED === 'true', REMINDER_AFTER_HOURS: toInt(process.env.REMINDER_AFTER_HOURS, 24), REMINDER_MESSAGE: process.env.REMINDER_MESSAGE || 'Hey {ping}! This ticket has been inactive for {hours} hours. Please provide an update or close the ticket.', PRIORITY_ENABLED: process.env.PRIORITY_ENABLED === 'true', DEFAULT_PRIORITY: process.env.DEFAULT_PRIORITY || 'normal', PRIORITY_HIGH_EMOJI: process.env.PRIORITY_HIGH_EMOJI || '🔴', PRIORITY_MEDIUM_EMOJI: process.env.PRIORITY_MEDIUM_EMOJI || '🟡', PRIORITY_LOW_EMOJI: process.env.PRIORITY_LOW_EMOJI || '🟢', AUTO_UNCLAIM_ENABLED: process.env.AUTO_UNCLAIM_ENABLED === 'true', AUTO_UNCLAIM_AFTER_HOURS: toInt(process.env.AUTO_UNCLAIM_AFTER_HOURS, 24), ALLOW_CLAIM_OVERWRITE: process.env.ALLOW_CLAIM_OVERWRITE === 'true', BUTTON_LABEL_CLOSE: process.env.BUTTON_LABEL_CLOSE || 'Close Ticket', BUTTON_LABEL_CLAIM: process.env.BUTTON_LABEL_CLAIM || 'Claim', BUTTON_LABEL_UNCLAIM: process.env.BUTTON_LABEL_UNCLAIM || 'Unclaim', BUTTON_EMOJI_CLOSE: process.env.BUTTON_EMOJI_CLOSE || '🔒', BUTTON_EMOJI_CLAIM: process.env.BUTTON_EMOJI_CLAIM || '📌', BUTTON_EMOJI_UNCLAIM: process.env.BUTTON_EMOJI_UNCLAIM || '🔓', EMBED_COLOR_OPEN: toInt(process.env.EMBED_COLOR_OPEN, 0x00FF00), EMBED_COLOR_CLAIMED: toInt(process.env.EMBED_COLOR_CLAIMED, 0xFFFF00), EMBED_COLOR_ESCALATED: toInt(process.env.EMBED_COLOR_ESCALATED, 0xFF6600), EMBED_COLOR_INFO: toInt(process.env.EMBED_COLOR_INFO, 0x1e2124), ADMIN_ID: process.env.ADMIN_ID || null, FORCE_CLOSE_TIMER: toInt(process.env.FORCE_CLOSE_TIMER_SECONDS, 60), GMAIL_POLL_INTERVAL_MS: toInt(process.env.GMAIL_POLL_INTERVAL_SECONDS, 30) * 1000, RENAME_LOG_CHANNEL_ID: process.env.RENAME_LOG_CHANNEL_ID || null, STAFF_THREAD_ENABLED: process.env.STAFF_THREAD_ENABLED === 'true', STAFF_THREAD_NAME: process.env.STAFF_THREAD_NAME || 'Staff Discussion', STAFF_THREAD_AUTO_ADD_ROLE: process.env.STAFF_THREAD_AUTO_ADD_ROLE === 'true', STAFF_THREAD_ROLE_ID: process.env.STAFF_THREAD_ROLE_ID || process.env.ROLE_ID_TO_PING || null, PIN_INITIAL_MESSAGE_ENABLED: process.env.PIN_INITIAL_MESSAGE_ENABLED === 'true', PIN_ESCALATION_MESSAGE_ENABLED: process.env.PIN_ESCALATION_MESSAGE_ENABLED === 'true', TRANSCRIPT_DM_TO_CREATOR: process.env.TRANSCRIPT_DM_TO_CREATOR === 'true', PIN_SUPPRESS_SYSTEM_MESSAGE: process.env.PIN_SUPPRESS_SYSTEM_MESSAGE === 'true', SETTINGS_PORT: toInt(process.env.SETTINGS_PORT, 12752), SETTINGS_ADMIN_PASSWORD: process.env.SETTINGS_ADMIN_PASSWORD || null, SETTINGS_DOMAIN: process.env.SETTINGS_DOMAIN || 'tickets.indifferentketchup.com', INTERNAL_API_PORT: toInt(process.env.INTERNAL_API_PORT, 12753), INTERNAL_API_SECRET: process.env.INTERNAL_API_SECRET || null }; const GAME_NAMES = (CONFIG.GAME_LIST || '') .split(',') .map(g => g.trim()) .filter(Boolean); const GAME_ALIASES = { '7D2D': '7 Days to Die', '7 days': '7 Days to Die', PZ: 'Project Zomboid', zomboid: 'Project Zomboid', MC: 'Minecraft', Ark: 'ARK: Survival Evolved', SOTF: 'Sons of the Forest', CS2: 'Counter-Strike 2' }; module.exports = { CONFIG, GAME_NAMES, GAME_ALIASES };