Files
broccolini-bot/docs/CRITICAL_FILES_AND_HOW_IT_WORKS.md
2026-02-17 21:49:58 -06:00

18 KiB
Raw Blame History

Critical Files & How Broccolini Bot Works

This document identifies the most critical files for understanding the repo and gives a thorough explanation of how the bot works end-to-end.


Most Critical Files (Read These First)

These are the files that give someone the fastest path to understanding the repo. Read in roughly this order.

1. README.md (repo root)

  • Why: Single source of truth for features, architecture diagram, config, commands, and troubleshooting.
  • What you get: High-level picture, env vars, Discord commands, tag/panel systems, database schema summary, and links to other docs.

2. broccolini-discord.js (entry point)

  • Why: Where the bot starts and where all major pieces are wired together.
  • What you get: Discord client setup, interactionCreate routing (buttons → commands → modals → context menus → autocomplete), messageCreate → Gmail reply handler, and ready logic: MongoDB connect, command registration, Gmail poll start, and background job intervals (auto-close, reminders, auto-unclaim). Also mounts the Express healthcheck and optional bOSScord API.

3. config.js

  • Why: All runtime configuration comes from here (env + defaults).
  • What you get: Single CONFIG object: Discord IDs, Gmail/MongoDB settings, automation toggles, message templates, button labels, priority/game lists, and guild-specific options. Test env is supported via ENV_FILE=.env.test.

4. models.js (Broccolini Bot section, ~line 793+)

  • Why: Data model defines what the bot persists and how tickets are represented.
  • What you get: Mongoose schemas for Ticket (gmailThreadId, discordThreadId, senderEmail, status, claimedBy, priority, escalation, etc.), TicketCounter, Transcript, Tag, CloseRequest, GuildSettings. Earlier in the file: User, Host, and other game/hosting models used by /accountinfo and external integrations.

5. gmail-poll.js

  • Why: This is the “email → Discord” bridge: how support emails become ticket channels.
  • What you get: poll(client) runs every 30s: lists unread primary inbox, skips messages from own address, parses From/Subject/body, strips quotes/footers, detects game from GAME_LIST, checks ticket limits and rate limits, gets next ticket number, creates Discord channel (or thread) and embed with Claim/Close buttons, saves Ticket + optional Transcript in MongoDB, marks email read. Overflow categories when a category hits 50 channels.

6. handlers/messages.js

  • Why: This is the “Discord → email” bridge: staff messages in a ticket become Gmail replies.
  • What you get: handleDiscordReply(message): ignores bots and non-ticket channels; looks up Ticket by discordThreadId; skips if ticket is Discord-origin (gmailThreadId.startsWith('discord-')); for email tickets, gets Gmail thread, finds last customer message, builds reply with staff name and content, calls sendGmailReply, and updates lastActivity.

7. services/gmail.js

  • Why: All Gmail API usage and outbound email logic.
  • What you get: OAuth2 client via getGmailClient(); sendGmailReply() (threaded reply with HTML, In-Reply-To/References); sendTicketClosedEmail() for closure notifications; optional sendTicketNotificationEmail() (e.g. priority high). Raw MIME construction and users.messages.send.

8. services/tickets.js

  • Why: Core ticket lifecycle and Discord channel/thread creation.
  • What you get: Ticket numbers (getNextTicketNumber), channel naming and Discord rate limit handling (2 renames per 10 min), ticket limits and overflow category selection, rate limit for ticket creation per user, createEmailTicketAsThread / createDiscordTicketAsThread, auto-close/reminder/auto-unclaim jobs, and helpers like updateTicketActivity, canRename, makeTicketName.

9. handlers/buttons.js

  • Why: Every button and ticket modal goes through here.
  • What you get: “Open Ticket” panel → modal (email, game, description); email routing buttons (thread vs category); Claim / Unclaim / Close (including close confirmation flow); priority and tag selects; escalation/deescalation; and handleTicketModal for creating a ticket from the panel. Integrates with commands.js for escalation and with tickets.js/gmail.js for close and notifications.

10. handlers/commands.js

  • Why: All slash commands and context menus are implemented here.
  • What you get: Staff check (requireStaffRole), then routing for /claim, /unclaim, /close, /priority, /topic, /escalate, /deescalate, /add, /remove, /transfer, /move, /tag, /response *, /panel, /email-routing, /accountinfo, /search, /stats, /backup, /export, /help, and context menu “Create Ticket From Message”. Uses tickets.js, guildSettings, analytics, and accountinfo/setup handlers.

11. commands/register.js

  • Why: Defines and registers every slash command and context menu with Discord.
  • What you get: Full list of command names, options, permissions, and context types. Run at startup so Discord has the latest command definitions.

12. db-connection.js

  • Why: MongoDB is required; this is the only place that connects and loads models.
  • What you get: connectMongoDB(uri), requires models.js, and wires error / disconnected / reconnected for resilience.

13. utils.js

  • Why: Shared parsing and formatting used by Gmail poll, Gmail service, and commands.
  • What you get: getCleanBody, extractRawEmail, stripEmailQuotes, stripMobileFooter, detectGame (from subject/body vs GAME_LIST), replaceVariables for tag/response templates ({ticket.user}, {staff.name}, etc.), getPriorityEmoji, getFormattedDate, escapeHtml, htmlToTextWithBlocks.

14. utils/ticketComponents.js

  • Why: Central place for Claim/Unclaim/Close (and related) button rows and embeds.
  • What you get: getTicketActionRow() and related builders so ticket channels and panels show consistent buttons and styling.

Supporting but still important


How the Bot Works (End-to-End)

Overview

Broccolini Bot is a Node.js support-ticket bot that connects Gmail, Discord, and MongoDB. Incoming support emails become Discord ticket channels (or threads); staff reply in Discord and their messages are sent back to the customer via Gmail. Tickets can also be created from Discord via a panel (no email). All ticket state is stored in MongoDB.


Startup sequence

  1. Load config
    config.js loads .env (or ENV_FILE), runs dotenv-expand, and exports CONFIG.

  2. Validate env
    broccolini-discord.js checks required vars (e.g. DISCORD_TOKEN, TICKET_CATEGORY_ID, Gmail OAuth). Missing required ones cause exit.

  3. Create Discord client
    Client is created with intents: Guilds, GuildMessages, MessageContent, GuildMembers; Partials.Channel for ticket channels that might not be in cache.

  4. Register event handlers

    • interactionCreate Buttons (accountinfo, setup, ticket actions), modals (setup, ticket creation), slash commands, context menus, autocomplete. Order matters: prefix checks (e.g. accountinfo, setup) run before generic button/command handlers.
    • messageCreate handleDiscordReply: Discord → Gmail for staff messages in ticket channels.
  5. ready

    • Connect MongoDB via db-connection.js (and load models.js).
    • Set debug log client and bOSScord API client.
    • If BOSSCORD_API_KEY is set, mount /api routes (e.g. bOSScord).
    • Call registerCommands() so slash commands and context menus are registered for the guild.
    • Start Gmail poll: poll(client) immediately and then setInterval(..., 30000).
    • If enabled: start hourly auto-close, 30-minute reminders, hourly auto-unclaim.
    • Express server is already created; it listens on CONFIG.PORT and serves GET /"Active" for healthchecks.
  6. No Gmail/MongoDB
    If MONGODB_URI is missing, the bot exits in ready. Gmail credentials are validated at startup but polling can fail later if tokens are bad.


Email → Discord (new ticket from email)

  1. Gmail poll (gmail-poll.js)

    • Every 30s, poll(client) uses Gmail API users.messages.list with is:unread category:primary.
    • For each message, fetches full message, parses From/Subject/body. Skips if From is the support address (and marks it read).
    • Extracts sender email and name; cleans body (strip reply quotes, mobile footers) via utils.js.
    • Detects game from subject/body using GAME_LIST (utils.detectGame).
    • Checks global and per-category ticket limits and per-user rate limit (services/tickets.js).
    • Gets next ticket number per sender from TicketCounter (getNextTicketNumber).
    • Decides where to create the ticket: thread vs category channel from getEmailRouting() (guild setting, can be set via /email-routing).
    • Creates the Discord channel or thread, posts an embed (subject, sender, game, ticket number) and action row (Claim, Close) from ticketComponents.js.
    • Saves a Ticket in MongoDB: gmailThreadId, discordThreadId (channel or thread id), senderEmail, subject, ticketNumber, game, status open, etc. Optionally creates Transcript placeholder.
    • Marks the Gmail message read so it is not processed again.
  2. Overflow
    Discord allows 50 channels per category. If the main ticket category is full, the bot uses EMAIL_TICKET_OVERFLOW_CATEGORY_IDS (and similar for Discord-origin tickets) to pick another category.


Discord → Email (staff reply)

  1. Message event
    When a message is sent in a channel, handleDiscordReply in handlers/messages.js runs.

  2. Filter
    Ignores bots and interactions. Finds a Ticket by discordThreadId === channel.id. If none, or if gmailThreadId.startsWith('discord-'), does nothing (Discord-origin tickets have no Gmail thread).

  3. Build reply
    Uses Gmail API users.threads.get to get the thread. Finds the last message from the customer (not from support). Reads To/Reply-To, Subject, Message-ID.

  4. Send
    sendGmailReply(threadId, content, recipientEmail, subject, discordUser, messageId) in services/gmail.js builds HTML (staff name, content, logo/signature), sets In-Reply-To/References for threading, and calls users.messages.send with threadId so the reply stays in the same Gmail thread.

  5. Activity
    updateTicketActivity(ticket.gmailThreadId) updates the tickets lastActivity for auto-close and reminder logic.


Ticket creation from Discord (panel)

  1. Panel
    Staff runs /panel (optionally with channel, type, title, description). The bot sends a message with an “Open Ticket” button (and optional styling).

  2. Button click
    User clicks the button. handlers/buttons.js shows a modal with: Account Email, Game, Description (and possibly priority).

  3. Modal submit
    handleTicketModal runs. Validates and applies rate limit (checkTicketCreationRateLimit). Creates a Ticket in MongoDB with gmailThreadId = 'discord-' + ... (no real Gmail thread). Gets next ticket number (by email or Discord user). Creates a Discord channel or thread (depending on panel type and guild settings), posts welcome embed and Claim/Close buttons, saves discordThreadId and other fields. No Gmail is involved for this ticket; handleDiscordReply explicitly skips when gmailThreadId.startsWith('discord-').


Claim / Unclaim / Close

  • Claim (button or /claim)
    Ticket is updated with claimedBy (user id or name). Channel may be renamed (respecting Discords 2 renames per 10 min). Claimed message is posted (template from CONFIG).

  • Unclaim (button or /unclaim)
    claimedBy is cleared; channel rename and message as above.

  • Close (button or /close)
    Close button often triggers a confirmation (e.g. “Are you sure?” with Confirm/Cancel). On confirm (or /force-close): build transcript of the channel, post it to the transcript channel, send closure email for email tickets via sendTicketClosedEmail, delete the Discord channel/thread, set ticket status to closed and clean up CloseRequest. Discord-origin tickets get no Gmail closure email.


Automation (background jobs)

  • Auto-close
    If AUTO_CLOSE_ENABLED, checkAutoClose runs hourly. Finds open tickets whose lastActivity is older than AUTO_CLOSE_AFTER_HOURS. For each, same flow as manual close (transcript, closure email if email ticket, delete channel, update ticket).

  • Reminders
    If REMINDER_ENABLED, checkReminders runs every 30 minutes. Finds open tickets inactive longer than REMINDER_AFTER_HOURS and not yet reminded. Sends reminder message to the channel and sets reminderSent.

  • Auto-unclaim
    If AUTO_UNCLAIM_ENABLED, checkAutoUnclaim runs hourly. Clears claimedBy on tickets that have been inactive for AUTO_UNCLAIM_AFTER_HOURS.

All of these use the Ticket models lastActivity (and optional reminderSent) and live in services/tickets.js.


Tags and saved responses

  • Tags
    /tag sets a ticket category (e.g. Server Down, Billing). Stored on the ticket and can be used in naming or display. Tag options come from CONFIG.TICKET_TAGS (from config / env).

  • Saved responses
    Stored in MongoDB (Tag collection for response name/content). /response create|edit|delete|list|send. When sending, utils.replaceVariables substitutes {ticket.user}, {staff.name}, {date}, etc. Autocomplete for response names is provided in handlers/commands.js.


Priority and escalation

  • Priority
    If PRIORITY_ENABLED, tickets have low/normal/medium/high. Stored on Ticket; embeds and channel naming can show priority emoji. Optional: send email when set to high (sendTicketNotificationEmail).

  • Escalation
    /escalate and /deescalate (or buttons) change escalationTier (0 → 1 → 2) and move the channel to escalation categories (e.g. EMAIL_ESCALATED_CATEGORY_ID, DISCORD_ESCALATED2_CHANNEL_ID). Channel rename and “escalated” message are posted. Optional escalation notification email.


Account info and other commands

  • /accountinfo
    Looks up a user by email or Discord ID (uses User and related models from models.js). Can post results to a dedicated channel; handler in handlers/accountinfo.js.

  • /email-routing
    Toggles where new email tickets are created: thread under a parent channel or channel in a category. Value is stored per guild in GuildSettings and read by gmail-poll.js via getEmailRouting().

  • /panel
    Sends a message with an “Open Ticket” button; panel type (thread vs channel) and target channel are options.

  • /search, /backup, /export
    Query or export tickets (by status, limit, etc.) and post results (e.g. to backup channel).

  • /stats
    Returns in-memory analytics (interactions, errors) from handlers/analytics.js.


Data flow summary


Dependencies (high level)

  • discord.js Client, channels, embeds, buttons, modals, slash commands, context menus.
  • googleapis Gmail API (OAuth2, list/get messages, send, threads).
  • mongoose MongoDB connection and Broccolini Bot + game/hosting models.
  • express Healthcheck server and bOSScord API mount.

For full setup, config, and troubleshooting, use README.md, QUICKSTART.md, and ENV_AND_SECURITY.md.