18 KiB
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,
interactionCreaterouting (buttons → commands → modals → context menus → autocomplete),messageCreate→ Gmail reply handler, andreadylogic: 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
CONFIGobject: Discord IDs, Gmail/MongoDB settings, automation toggles, message templates, button labels, priority/game lists, and guild-specific options. Test env is supported viaENV_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
/accountinfoand 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 fromGAME_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 bydiscordThreadId; 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, callssendGmailReply, and updateslastActivity.
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; optionalsendTicketNotificationEmail()(e.g. priority high). Raw MIME construction andusers.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 likeupdateTicketActivity,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
handleTicketModalfor creating a ticket from the panel. Integrates withcommands.jsfor escalation and withtickets.js/gmail.jsfor 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”. Usestickets.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), requiresmodels.js, and wireserror/disconnected/reconnectedfor 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 vsGAME_LIST),replaceVariablesfor 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
- services/guildSettings.js – Guild-specific settings (e.g. email routing: thread vs category), cached and persisted in MongoDB.
- services/debugLog.js – Structured logging and optional Discord debugging channel.
- handlers/accountinfo.js –
/accountinfoand lookup logic (website user / Discord link). - handlers/analytics.js – In-memory interaction/error tracking and
/stats. - handlers/setup.js – Guild setup flow (buttons/modals/selects).
- game-options.json – Game list used for dropdowns/options.
- QUICKSTART.md – Short path to first reply, panel, tags, priority.
- ENV_AND_SECURITY.md – Test env workflow and security/agent rules.
- 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
-
Load config
config.js loads.env(orENV_FILE), runs dotenv-expand, and exportsCONFIG. -
Validate env
broccolini-discord.js checks required vars (e.g.DISCORD_TOKEN,TICKET_CATEGORY_ID, Gmail OAuth). Missing required ones cause exit. -
Create Discord client
Client is created with intents: Guilds, GuildMessages, MessageContent, GuildMembers; Partials.Channel for ticket channels that might not be in cache. -
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.
-
ready- Connect MongoDB via db-connection.js (and load models.js).
- Set debug log client and bOSScord API client.
- If
BOSSCORD_API_KEYis set, mount/apiroutes (e.g. bOSScord). - Call
registerCommands()so slash commands and context menus are registered for the guild. - Start Gmail poll:
poll(client)immediately and thensetInterval(..., 30000). - If enabled: start hourly auto-close, 30-minute reminders, hourly auto-unclaim.
- Express server is already created; it listens on
CONFIG.PORTand servesGET /→"Active"for healthchecks.
-
No Gmail/MongoDB
IfMONGODB_URIis missing, the bot exits inready. Gmail credentials are validated at startup but polling can fail later if tokens are bad.
Email → Discord (new ticket from email)
-
Gmail poll (gmail-poll.js)
- Every 30s,
poll(client)uses Gmail APIusers.messages.listwithis: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, statusopen, etc. Optionally creates Transcript placeholder. - Marks the Gmail message read so it is not processed again.
- Every 30s,
-
Overflow
Discord allows 50 channels per category. If the main ticket category is full, the bot usesEMAIL_TICKET_OVERFLOW_CATEGORY_IDS(and similar for Discord-origin tickets) to pick another category.
Discord → Email (staff reply)
-
Message event
When a message is sent in a channel,handleDiscordReplyin handlers/messages.js runs. -
Filter
Ignores bots and interactions. Finds a Ticket bydiscordThreadId === channel.id. If none, or ifgmailThreadId.startsWith('discord-'), does nothing (Discord-origin tickets have no Gmail thread). -
Build reply
Uses Gmail APIusers.threads.getto get the thread. Finds the last message from the customer (not from support). Reads To/Reply-To, Subject, Message-ID. -
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 callsusers.messages.sendwiththreadIdso the reply stays in the same Gmail thread. -
Activity
updateTicketActivity(ticket.gmailThreadId)updates the ticket’slastActivityfor auto-close and reminder logic.
Ticket creation from Discord (panel)
-
Panel
Staff runs/panel(optionally with channel, type, title, description). The bot sends a message with an “Open Ticket” button (and optional styling). -
Button click
User clicks the button. handlers/buttons.js shows a modal with: Account Email, Game, Description (and possibly priority). -
Modal submit
handleTicketModalruns. Validates and applies rate limit (checkTicketCreationRateLimit). Creates a Ticket in MongoDB withgmailThreadId = '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, savesdiscordThreadIdand other fields. No Gmail is involved for this ticket;handleDiscordReplyexplicitly skips whengmailThreadId.startsWith('discord-').
Claim / Unclaim / Close
-
Claim (button or
/claim)
Ticket is updated withclaimedBy(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)
claimedByis 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 viasendTicketClosedEmail, delete the Discord channel/thread, set ticket status toclosedand clean up CloseRequest. Discord-origin tickets get no Gmail closure email.
Automation (background jobs)
-
Auto-close
IfAUTO_CLOSE_ENABLED,checkAutoCloseruns hourly. Finds open tickets whoselastActivityis older thanAUTO_CLOSE_AFTER_HOURS. For each, same flow as manual close (transcript, closure email if email ticket, delete channel, update ticket). -
Reminders
IfREMINDER_ENABLED,checkRemindersruns every 30 minutes. Finds open tickets inactive longer thanREMINDER_AFTER_HOURSand not yet reminded. Sends reminder message to the channel and setsreminderSent. -
Auto-unclaim
IfAUTO_UNCLAIM_ENABLED,checkAutoUnclaimruns hourly. ClearsclaimedByon tickets that have been inactive forAUTO_UNCLAIM_AFTER_HOURS.
All of these use the Ticket model’s lastActivity (and optional reminderSent) and live in services/tickets.js.
Tags and saved responses
-
Tags
/tagsets a ticket category (e.g. Server Down, Billing). Stored on the ticket and can be used in naming or display. Tag options come fromCONFIG.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.replaceVariablessubstitutes{ticket.user},{staff.name},{date}, etc. Autocomplete for response names is provided inhandlers/commands.js.
Priority and escalation
-
Priority
IfPRIORITY_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
/escalateand/deescalate(or buttons) changeescalationTier(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 frommodels.js). Can post results to a dedicated channel; handler inhandlers/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 viagetEmailRouting(). -
/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) fromhandlers/analytics.js.
Data flow summary
- Gmail → Discord: gmail-poll.js (poll) → services/gmail.js (read), services/tickets.js (create channel + Ticket), utils.js (parse/detect game).
- Discord → Gmail: handlers/messages.js → services/gmail.js (
sendGmailReply), services/tickets.js (updateTicketActivity). - Discord-only ticket: handlers/buttons.js (modal) → services/tickets.js (create channel + Ticket with
gmailThreadId: 'discord-...'). - All interactions: broccolini-discord.js (routing) → handlers/buttons.js or handlers/commands.js → services/tickets.js, services/guildSettings.js, services/gmail.js, and 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, QUICKSTART.md, and ENV_AND_SECURITY.md.