# 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**](../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**](../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**](../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**](../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**](../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**](../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**](../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**](../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**](../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**](../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**](../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**](../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**](../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**](../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 - [**services/guildSettings.js**](../services/guildSettings.js) – Guild-specific settings (e.g. email routing: thread vs category), cached and persisted in MongoDB. - [**services/debugLog.js**](../services/debugLog.js) – Structured logging and optional Discord debugging channel. - [**handlers/accountinfo.js**](../handlers/accountinfo.js) – `/accountinfo` and lookup logic (website user / Discord link). - [**handlers/analytics.js**](../handlers/analytics.js) – In-memory interaction/error tracking and `/stats`. - [**handlers/setup.js**](../handlers/setup.js) – Guild setup flow (buttons/modals/selects). - [**game-options.json**](../game-options.json) – Game list used for dropdowns/options. - [**QUICKSTART.md**](QUICKSTART.md) – Short path to first reply, panel, tags, priority. - [**ENV_AND_SECURITY.md**](ENV_AND_SECURITY.md) – Test env workflow and security/agent rules. - [**PROJECT_STRUCTURE.md**](PROJECT_STRUCTURE.md) – File/directory layout reference. --- ## 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](../config.js) loads `.env` (or `ENV_FILE`), runs dotenv-expand, and exports `CONFIG`. 2. **Validate env** [broccolini-discord.js](../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](../db-connection.js) (and load [models.js](../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](../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](../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](../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](../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 ticket’s `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](../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 Discord’s 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 model’s `lastActivity` (and optional `reminderSent`) and live in [services/tickets.js](../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](../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 - **Gmail → Discord:** [gmail-poll.js](../gmail-poll.js) (poll) → [services/gmail.js](../services/gmail.js) (read), [services/tickets.js](../services/tickets.js) (create channel + Ticket), [utils.js](../utils.js) (parse/detect game). - **Discord → Gmail:** [handlers/messages.js](../handlers/messages.js) → [services/gmail.js](../services/gmail.js) (`sendGmailReply`), [services/tickets.js](../services/tickets.js) (`updateTicketActivity`). - **Discord-only ticket:** [handlers/buttons.js](../handlers/buttons.js) (modal) → [services/tickets.js](../services/tickets.js) (create channel + Ticket with `gmailThreadId: 'discord-...'`). - **All interactions:** [broccolini-discord.js](../broccolini-discord.js) (routing) → [handlers/buttons.js](../handlers/buttons.js) or [handlers/commands.js](../handlers/commands.js) → [services/tickets.js](../services/tickets.js), [services/guildSettings.js](../services/guildSettings.js), [services/gmail.js](../services/gmail.js), and [models.js](../models.js) (Mongoose). --- ### 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](../README.md), [QUICKSTART.md](QUICKSTART.md), and [ENV_AND_SECURITY.md](ENV_AND_SECURITY.md).