# Broccolini Bot A **Node.js** Discord bot that unifies **Gmail**, **Discord**, and **MongoDB** for support ticketing. Incoming emails become Discord ticket channels; staff messages in those channels are sent back to customers via Gmail. Discord-originated tickets (panels, context menu) live entirely in Discord. State is stored in MongoDB via Mongoose. Built for game-server hosting support (Indifferent Broccoli), with game detection from email content, tiered escalation, optional **per-staff notification channels** (reply alerts with cooldown + unclaimed-ticket digests), saved responses, `/tag` categorization, claimer emoji in channel names (`STAFF_EMOJIS`), and automation (auto-close, reminders, auto-unclaim, claim timeout). **Jump to:** [Features](#features) · [Quick start](#quick-start) · [Configuration](#configuration) · [Staff notifications](#staff-notification-channels--reply-alerts) · [Broccolini settings page](#broccolini-settings-page) · [Commands](#discord-commands) · [Project layout](#project-structure) --- ## Table of contents - [Features](#features) - [Architecture](#architecture) - [Prerequisites](#prerequisites) - [Quick start](#quick-start) - [Installation](#installation) - [Configuration](#configuration) - [Staff notification channels & reply alerts](#staff-notification-channels--reply-alerts) - [Broccolini settings page](#broccolini-settings-page) - [Running the bot (test and Docker)](#running-the-bot-test-and-docker) - [Discord commands](#discord-commands) - [Ticket UI (buttons & modals)](#ticket-ui-buttons--modals) - [Tag & response system](#tag--response-system) - [Panel system](#panel-system) - [Channel renames & moves (rate limits)](#channel-renames--moves-rate-limits) - [Project structure](#project-structure) - [Database collections](#database-collections) - [HTTP: healthcheck & optional API](#http-healthcheck--optional-api) - [Gmail OAuth refresh token](#gmail-oauth-refresh-token) - [Documentation in `docs/`](#documentation-in-docs) - [Development & CI](#development--ci) - [Troubleshooting](#troubleshooting) - [References](#references) - [License](#license) --- ## Features ### Email → Discord - Polls Gmail about every **30 seconds** for new unread **primary** mail (`gmail-poll.js`). - Creates a **Discord text channel** (or thread, per guild settings) per email ticket, with overflow category support when a category is full. - Detects **game** from subject/body using `GAME_LIST` and built-in aliases in `config.js`. - Posts welcome + action **buttons** (Close, Claim, Escalate / De-escalate where applicable). ### Discord → Gmail - For **email-sourced** tickets, staff messages in the ticket channel are **forwarded** to the customer via Gmail (threaded). - **Discord-only** tickets (`gmailThreadId` prefix `discord-` / `discord-msg-`) do not use Gmail for replies; conversation stays in Discord. ### Ticket management - **Claim / Unclaim** via buttons (not slash commands); optional claim overwrite, **claim timeout**, and auto-unclaim. - **Priority** (`low` / `normal` / `medium` / `high`) with configurable emojis and `/priority`. - **Escalation**: tier 2 and tier 3 categories (separate IDs for email vs Discord where configured); slash `/escalate` and in-channel buttons. - **De-escalation** one step at a time (`/deescalate` or button). - **Close** with confirmation; **force-close** for admins. - **Transcripts** posted to a configured channel; closure email for email tickets. - **Auto-close**, **inactivity reminders**, **auto-unclaim** (all optional via env). ### Staff notifications & alerts (optional) - **Per-staff notification channels**: **`/notification add`** creates a **dedicated text channel** per staff member under `STAFF_NOTIFICATION_CATEGORY_ID` and stores it in **`StaffNotification`** (MongoDB). When a **non-staff** user replies in a ticket claimed by someone with a notification channel, the bot posts an alert there (subject to **per-ticket cooldown** via `/notification set` or admin **`/staffnotification`**). - **Unclaimed digests**: a background job runs **every 30 minutes** and, if `UNCLAIMED_REMINDER_THRESHOLDS` is set, posts **unclaimed ticket** digests to those same channels when tickets cross age thresholds. - **DM reply alerts**: **`/notifydm`** toggles optional **DM** alerts to the claimer on customer reply (separate from the notification channel); stored in **`StaffSettings`**. - **Staff threads** (optional): when `STAFF_THREAD_ENABLED` is true, each ticket channel can get a private **staff-only thread** named `STAFF_THREAD_NAME`; on claim, the claimer can be added to that thread, and (optionally) all members of `STAFF_THREAD_ROLE_ID` are auto-added. - **Pins** (optional): `PIN_INITIAL_MESSAGE_ENABLED` and `PIN_ESCALATION_MESSAGE_ENABLED` enable auto-pinning of the ticket welcome message and escalation messages; `PIN_SUPPRESS_SYSTEM_MESSAGE` hides the default “X pinned a message” system notice. - **Chat monitoring & surge detection**: see [Patterns, surge & chat alerts](#patterns-surge--chat-alerts) for automatic alerts about busy chats, surging games, backlogs, and no-staff situations. See [Staff notification channels](#staff-notification-channels--reply-alerts) and [Patterns, surge & chat alerts](#patterns-surge--chat-alerts) for details. **Note:** Older docs referred to per-staffer **mirror** channels driven by `STAFF_CATEGORIES`. In current `config.js` that map is **deprecated and always empty**, and **`createStaffChannel` is not called** from the claim flow—**`staffChannelId` on tickets is effectively unused.** Reply alerts use **`StaffNotification`** channels instead, and staff discussion happens in optional **staff threads**. ### Extras - **`/panel`**: “Open ticket” UI (modal collects email, game, description). - **`/tag`**: ticket category dropdown; **`/response`**: saved templates with variable substitution. - **`/setup`**: setup wizard for guild defaults. - **`/accountinfo`**: website account lookup (email or Discord user). - **`/stats`**, **`/search`**, **`/backup`**, **`/export`**, **`/email-routing`**. - **Context menus**: create ticket from message; view user tickets. - **Optional REST API** under `/api` when the relevant API key env vars are set (see `.env.example`). --- ## Architecture ``` ┌─────────────────────────────────────────────────────────────────┐ │ BROCCOLINI BOT │ ├─────────────────────────────────────────────────────────────────┤ │ Gmail (inbox) ──► gmail-poll.js ──► Discord ticket channels │ │ │ ▲ │ │ ▼ │ │ │ services/gmail.js ◄── handlers/messages.js │ │ services/tickets.js handlers/buttons.js │ │ services/channelQueue.js │ │ services/staffNotifications.js │ │ │ │ │ ▼ │ │ MongoDB (Mongoose) ◄── models.js │ │ │ │ Express: GET / → "Active" ; optional /api → routes/ │ └─────────────────────────────────────────────────────────────────┘ ``` **Typical email ticket lifecycle** 1. New unread mail → poll creates Discord channel + `Ticket` document. 2. Staff reply in channel → message handler sends Gmail reply (email tickets only). 3. Close confirmed → transcript, optional closure email, channel delete; DB marked closed. --- ## Prerequisites | Requirement | Notes | |-------------|--------| | **Node.js** | **18+**; Docker image uses **20** (`Dockerfile`). | | **npm** | `npm install` locally; `npm ci --omit=dev` in Docker. | | **MongoDB** | Self-hosted; `MONGODB_URI` required at startup. | | **Discord application** | Bot token, application ID; intents: **Message Content**, **Server Members**; also Guilds + Guild Messages. | | **Google Cloud** | Gmail API enabled; OAuth2 client ID/secret + refresh token for the support mailbox. | --- ## Quick start ```bash git clone cd broccolini-bot npm install cp .env.example .env ``` 1. Fill **Discord** (`DISCORD_TOKEN` or `DISCORD_BOT_TOKEN`, `DISCORD_APPLICATION_ID`, `DISCORD_GUILD_ID`, categories, `ROLE_ID_TO_PING`, transcript/log channels). 2. Fill **MongoDB** (`MONGODB_URI`). 3. Fill **Google** OAuth (`GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `REFRESH_TOKEN`, `MY_EMAIL`) — use `node get-refresh-token.js` once if needed. 4. Run `npm start`. 5. In Discord, use **`/setup`** or verify categories and roles manually. Restart after **any** `.env` change. After changing **slash command definitions**, restart so **`registerCommands()`** re-registers with Discord. --- ## Installation Same as quick start. Optional: - **Test env:** copy `.env.test.example` → `.env.test`. On **Unix shells**: `npm run start:test` (sets `ENV_FILE`). On **Windows PowerShell**: `$env:ENV_FILE='.env.test'; node broccolini-discord.js` (or set `ENV_FILE` in the environment your process manager uses). **`npm run test-mongodb:test`** has the same `ENV_FILE` pattern. - **1Password CLI:** `npm run start:1p` / `start:test:1p` inject secrets (see [docs/setup/1PASSWORD.md](docs/setup/1PASSWORD.md)). **Do not commit** `.env` or `.env.test`. AI/agents should not edit production `.env` without explicit approval; see [ENV_AND_SECURITY.md](docs/setup/ENV_AND_SECURITY.md). --- ## Configuration Configuration is **environment variables** only, loaded in [`config.js`](config.js) as `CONFIG`. Names below match `.env.example` unless noted. ### Discord (core) | Variable | Required | Description | |----------|----------|-------------| | `DISCORD_TOKEN` | Yes* | Bot token (*or `DISCORD_BOT_TOKEN`, first non-empty wins after trim). | | `DISCORD_APPLICATION_ID` | Yes | Used as `CLIENT_ID` for REST command registration. | | `DISCORD_GUILD_ID` | Yes | Guild where slash commands are registered. | | `TICKET_CATEGORY_ID` | Yes | Default category for **email** tickets (startup validates this). | | `DISCORD_TICKET_CATEGORY_ID` | No | Category for **Discord** panel/context tickets (falls back to `TICKET_CATEGORY_ID`). | | `EMAIL_THREAD_CHANNEL_ID` / `DISCORD_THREAD_CHANNEL_ID` | No | Parent **text** channels for thread-style tickets, when used. | | `EMAIL_TICKET_OVERFLOW_CATEGORY_IDS` | No | Comma-separated extra categories when the main is full (50 channels). | | `DISCORD_TICKET_OVERFLOW_CATEGORY_IDS` | No | Same for Discord ticket category. | | `ROLE_ID_TO_PING` | Yes | Support role pinged on new tickets; alias `ROLE_TO_PING_ID` in code. | | `ADDITIONAL_STAFF_ROLES` | No | Extra role IDs treated as staff for commands. | | `BLACKLISTED_ROLES` | No | Roles blocked from opening tickets. | | `TRANSCRIPT_CHANNEL_ID` | No | Where transcripts are posted on close. | | `LOGGING_CHANNEL_ID` | No | Ticket lifecycle logs. | | `DEBUGGING_CHANNEL_ID` | No | Optional error/debug forwarding. | | `BACKUP_EXPORT_CHANNEL_ID` | No | Target for `/backup` and `/export`. | | `ACCOUNT_INFO_CHANNEL_ID` | No | Account info flows. | ### Escalation categories | Variable | Description | |----------|-------------| | `EMAIL_ESCALATED_CATEGORY_ID` | Legacy fallback; alias `ESCALATED_CATEGORY_ID`. | | `DISCORD_ESCALATED_CATEGORY_ID` | Discord fallback tier-2–style bucket. | | `DISCORD_ESCALATED2_CHANNEL_ID` | Tier **2** placement for Discord tickets (or + fallback category). | | `EMAIL_ESCALATED2_CHANNEL_ID` | Tier **2** for email tickets (env name says CHANNEL for legacy reasons). | | `DISCORD_ESCALATED3_CHANNEL_ID` | Tier **3** Discord. | | `EMAIL_ESCALATED3_CHANNEL_ID` | Tier **3** email. | Slash `/escalate` and buttons require the appropriate tier IDs for **non-thread** channels (threads skip category moves). ### Staff notifications, claimer display, admin | Variable | Description | |----------|-------------| | `STAFF_NOTIFICATION_CATEGORY_ID` | Category where **`/notification add`** creates per-staffer notification channels. | | `STAFF_EMOJIS` | Comma-separated `discordUserId:emoji` pairs; used in **channel name** when a ticket is claimed. | | `CLAIMER_EMOJI_FALLBACK` | Emoji if the claimer has no `STAFF_EMOJIS` entry. | | `ADMIN_ID` | Discord user ID allowed to use **`/staffnotification`** (override cooldown for another member). | | `UNCLAIMED_REMINDER_THRESHOLDS` | Comma-separated **hours** (e.g. `1,2,4`); drives unclaimed ticket alerts into notification channels. | ### Logging & observability | Variable | Description | |----------|-------------| | `GMAIL_LOG_CHANNEL_ID` | Channel for Gmail poll activity logs. | | `AUTOMATION_LOG_CHANNEL_ID` | Channel for auto-close/auto-unclaim/reminder logs. | | `RENAME_LOG_CHANNEL_ID` | Channel for channel rename queue logs. | | `SECURITY_LOG_CHANNEL_ID` | Channel for security/audit logs. | | `SYSTEM_LOG_CHANNEL_ID` | Channel for bot lifecycle logs (startup, shutdown, DB events). | ### Pattern detection & surge/chat alerts Core behaviour is configured via `.env.example`; high level: - **Pattern detection** (`patternStore.js`, `patternChecker.js`): - `USER_PATTERNS_CHANNEL_ID`, `GAME_PATTERNS_CHANNEL_ID`, `TAG_PATTERNS_CHANNEL_ID`, `ESCALATION_PATTERNS_CHANNEL_ID`, `STAFF_PATTERNS_CHANNEL_ID`, `COMBINED_PATTERNS_CHANNEL_ID` select where pattern embeds are posted. - Threshold envs like `PATTERN_USER_TICKET_THRESHOLD`, `PATTERN_GAME_TICKET_THRESHOLD`, `PATTERN_UNCLAIMED_HOURS`, `PATTERN_ESCALATION_THRESHOLD`, `PATTERN_RAPID_CLOSE_SECONDS` tune when alerts fire. - Windows (`today`, `week`, `month`) reset automatically via scheduled timers in `patternStore.scheduleResets()`. - **Surge detection** (`surgeChecker.js`): - `ALL_STAFF_CHANNEL_ID` is the primary surge-alert channel; `SURGE_ROLE_ID` is pinged when set. - `SURGE_TICKET_COUNT` / `SURGE_TICKET_WINDOW_MINUTES`, `SURGE_GAME_TICKET_COUNT` / `SURGE_GAME_TICKET_WINDOW_MINUTES`, `SURGE_STALE_*`, `SURGE_NEEDS_RESPONSE_*`, `SURGE_UNCLAIMED_*`, `SURGE_TIER3_UNCLAIMED_MINUTES`, `SURGE_COOLDOWN_MINUTES` control volume/backlog alerts. - `SURGE_NO_STAFF_OPEN_TICKET_THRESHOLD`, `SURGE_NO_STAFF_COOLDOWN_MINUTES`, `STAFF_IDS`, and `STAFF_DND_COUNTS_AS_AVAILABLE` drive “no staff available” alerts (presence-based with message activity fallback). - **Chat monitoring** (`chatAlertChecker.js`): - `CHAT_ALERT_CHANNEL_IDS` lists channels to monitor. - `CHAT_ALERT_MESSAGE_COUNT`, `CHAT_ALERT_HOURS_WITHOUT_RESPONSE`, `CHAT_ALERT_COOLDOWN_MINUTES` configure when to send chat-attention alerts to `ALL_STAFF_CHAT_ALERT_CHANNEL_ID`. ### Google / Gmail | Variable | Required | Description | |----------|----------|-------------| | `GOOGLE_CLIENT_ID` | Yes | OAuth2 client ID. | | `GOOGLE_CLIENT_SECRET` | Yes | OAuth2 secret. | | `REFRESH_TOKEN` | Yes | Long-lived refresh for the inbox account. | | `MY_EMAIL` | Yes | Canonical support address (lowercased in config). | ### MongoDB | Variable | Required | |----------|----------| | `MONGODB_URI` | Yes | Test: `npm run test-mongodb` (optionally with `ENV_FILE` / `.env.test` as above). ### Server & optional HTTP API | Variable | Default | Description | |----------|---------|-------------| | `DISCORD_ONLY_PORT` | `5000` | Express listen port (`CONFIG.PORT`). | | `HEALTHCHECK_HOST` | *(all interfaces)* | e.g. `127.0.0.1` for local-only bind. | Additional variables for mounting **`/api`** (API key, CORS, etc.) are listed in `.env.example` if you use that integration. ### Messaging & branding See `.env.example` for defaults: `ESCALATION_MESSAGE` (`{support_name}`), `TICKET_WELCOME_MESSAGE`, `TICKET_CLAIMED_MESSAGE` / `TICKET_UNCLAIMED_MESSAGE` (`{staff_mention}`, `{staff_name}`), `DISCORD_CLOSE_MESSAGE`, `DISCORD_TRANSCRIPT_MESSAGE` (`{channel_name}`, `{email}`, `{date_opened}`, `{date_closed}`), `EMAIL_SIGNATURE` (`\n` → `
`), embed color hex vars, button labels/emojis, `SUPPORT_NAME`, `LOGO_URL`. ### Automation & limits - **Auto-close:** `AUTO_CLOSE_ENABLED`, `AUTO_CLOSE_AFTER_HOURS`, `AUTO_CLOSE_MESSAGE`. - **Reminders:** `REMINDER_ENABLED`, `REMINDER_AFTER_HOURS`, `REMINDER_MESSAGE` (`{ping}`, `{hours}`). - **Limits:** `GLOBAL_TICKET_LIMIT`, `TICKET_LIMIT_PER_CATEGORY`, `RATE_LIMIT_TICKETS_PER_USER`, `RATE_LIMIT_WINDOW_MINUTES`. - **Claim:** `ALLOW_CLAIM_OVERWRITE`, `AUTO_UNCLAIM_*`, `CLAIM_TIMEOUT_ENABLED`, `CLAIM_TIMEOUT_HOURS`. - **Priority:** `PRIORITY_ENABLED`, `DEFAULT_PRIORITY`, `PRIORITY_*_EMOJI`. ### Game list `GAME_LIST=comma,separated,names` — used for detection/normalization in email handling (plus aliases in `config.js`). ### Thread-style tickets (legacy) `USE_THREADS`, `THREAD_PARENT_CHANNEL` (see `.env.example`) — optional legacy paths; primary behavior is also governed by **`GuildSettings.emailRouting`** (`/email-routing`: `thread` | `category`). --- ## Staff notification channels & reply alerts When `STAFF_NOTIFICATION_CATEGORY_ID` is set: 1. **`/notification add`** (with a target member) creates a channel under that category and saves `userId` → `channelId` + default **cooldown** in **`StaffNotification`**. 2. **`/notification set hours:`** (1–6) updates the cooldown between **reply alerts** for that user’s claimed tickets (same ticket keys off `gmailThreadId`). 3. **`/staffnotification`** (admin, `ADMIN_ID`) sets cooldown for **another** staff member. 4. On **messageCreate**, if the ticket has a `claimerId` and the author is **not** detected as having `ROLE_ID_TO_PING`, **`notifyStaffOfReply`** may post in the claimer’s notification channel (respecting cooldown). 5. **Every 30 minutes**, **`notifyAllStaffUnclaimed`** evaluates open tickets with `claimedBy: null` against `UNCLAIMED_REMINDER_THRESHOLDS` and posts to all configured notification channels (tracks sent thresholds on the ticket in `unclaimedReminderssent`). **`/notifydm`** toggles **`StaffSettings.notifyDm`** for the invoking user; when enabled, claimers can also receive a **DM** on customer reply (in addition to any notification channel). --- ## Broccolini settings page The repo includes an optional **Broccolini settings** web UI under `settings-site/` for configuring the bot without editing `.env` directly. - Runs as a small Express app (`settings-site/server.js`) on `SETTINGS_PORT` and talks to the bot’s internal API on `INTERNAL_API_PORT` using `INTERNAL_API_SECRET`. - Serves a password-protected dashboard (`SETTINGS_ADMIN_PASSWORD`) where you can adjust Discord channels, categories, Gmail credentials, ticket behavior, surge alerts, pattern thresholds, appearance, staff options, and advanced settings. - Changes are sent to the bot’s internal `/internal/config` endpoints and can be saved as pending, applied immediately, or saved and paired with an immediate or scheduled restart. To use it, run `node settings-site/server.js` alongside the bot (or via Docker), set the `SETTINGS_*` and `INTERNAL_API_*` variables as in `.env.example`, and put it behind HTTPS with your preferred reverse proxy. --- ## Running the bot (test and Docker) ```bash npm start # or node broccolini-discord.js ``` **Test / alternate env file:** see [Installation](#installation) for `ENV_FILE` on Windows vs Unix. ```bash npm run test-mongodb ``` **Docker** (see [`Dockerfile`](Dockerfile)): ```bash docker build -t broccolini-bot . docker run --env-file .env -p 5000:5000 broccolini-bot ``` Ensure `MONGODB_URI` and Discord token are available inside the container. A sample [`docker-compose.yml`](docker-compose.yml) exists—adjust **ports** and **env_file** for your host (do not copy production-specific bind addresses into new deployments without review). --- ## Discord commands Most commands require **staff** (`ROLE_ID_TO_PING` or `ADDITIONAL_STAFF_ROLES`). **`/help`** is available more broadly per registration. | Command | Description | |---------|-------------| | **`/setup`** | Guild setup wizard (panel, role, category, transcript channel, etc.). | | **`/panel`** | Post a ticket **Open** button in a channel (optional `type`: thread / category / both; custom title/description). | | **`/email-routing`** | Choose whether **new email** tickets create **threads** vs **category channels** (`GuildSettings` in DB). | | **`/escalate`** | **Required:** `level` (Tier 2 or Tier 3), `action` (`unclaim` clears `claimedBy` + `claimerId` after escalation, `keep` preserves claim). | | **`/deescalate`** | Step down one tier (tier 3 → 2 → normal). | | **`/notifydm`** | `setting`: `on` / `off` — DM when a **non-staff** user replies in a ticket you claimed. | | **`/notification`** | Subcommands: `set` (cooldown hours), `add` (create notification channel for a member). | | **`/staffnotification`** | Admin only (`ADMIN_ID`); override another member’s notification cooldown. | | **`/add`**, **`/remove`** | Add/remove user overwrites on the current ticket channel. | | **`/transfer`** | Set `claimedBy` to another staff member (must have staff role). | | **`/move`** | Move channel to another **category** (direct `setParent`). | | **`/force-close`** | Close without button confirmation (still archives transcript best-effort). | | **`/topic`** | Set Discord channel topic. | | **`/priority`** | `low` / `normal` / `medium` / `high`. | | **`/tag`** | Set ticket tag category from dropdown (choices from `TICKET_TAGS` in `config.js`). | | **`/response`** | Subcommands: `send`, `create`, `edit`, `delete`, `list` (saved responses). | | **`/accountinfo`** | Subcommands: `email`, `discord`. | | **`/search`** | Search tickets by email, subject, or number. | | **`/stats`** | Bot analytics snapshot. | | **`/backup`**, **`/export`** | Post exports to `BACKUP_EXPORT_CHANNEL_ID`. | | **`/help`** | In-bot command summary embed. | **Context menus** - **Create Ticket From Message** — opens a ticket prefilled from a message. - **View User Tickets** — lists recent tickets for a user (by sender tag match). --- ## Ticket UI (buttons & modals) - **Open ticket** (panel): modal fields are **account email**, **game**, **description**. - In ticket channels: **Close**, **Claim/Unclaim**, **Escalate** (tier choice), **De-escalate** as built in [`utils/ticketComponents.js`](utils/ticketComponents.js) / [`handlers/buttons.js`](handlers/buttons.js). - **Email routing** and **tag delete** confirmations use additional button custom IDs. --- ## Tag & response system ### `/tag` Sets `ticketTag` from the fixed list in `config.js` (`TICKET_TAGS`). Channel naming may incorporate tag/priority emojis via ticket naming logic. ### `/response` Templates support variables such as `{ticket.user}`, `{ticket.email}`, `{ticket.number}`, `{ticket.subject}`, `{staff.name}`, `{staff.mention}`, `{server.name}`, `{date}`, `{time}` (see [`utils.js`](utils.js) / handler docs). --- ## Panel system 1. Run **`/panel`** targeting a channel (and optional style: thread-only, category-only, or both buttons). 2. User clicks **Open ticket** → modal → bot creates thread or channel per configuration. 3. Welcome embeds + action row are posted; `Ticket` stores `discordThreadId`, `ticketNumber`, etc. --- ## Channel renames & moves (rate limits) Discord allows **two renames per 10 minutes** per channel. The bot serializes renames/moves through [`services/channelQueue.js`](services/channelQueue.js) (`p-queue`). If rename is blocked, staff see a message with a **relative time** to retry. --- ## Project structure ``` broccolini-bot/ ├── broccolini-discord.js # Entry: Discord client, Express, Gmail poll interval, jobs ├── config.js # Env → CONFIG (game lists, TICKET_TAGS, STAFF_EMOJIS map, …) ├── db-connection.js # Mongo connect + require models ├── models.js # Mongoose schemas (Ticket, Tag, StaffSettings, StaffNotification, …) ├── utils.js # Email/game helpers, template variables ├── utils/ticketComponents.js # Action row builders ├── gmail-poll.js # Ingest Gmail → Discord ticket creation ├── get-refresh-token.js # One-shot OAuth refresh token helper ├── commands/register.js # Slash + context menu registration (discord.js v14) ├── handlers/ │ ├── buttons.js # Claim/close/modals/escalate buttons, ticket create modal │ ├── commands.js # Slash handlers, runEscalation/runDeescalation │ ├── messages.js # Staff ↔ Gmail relay; notifydm; notification alerts │ ├── accountinfo.js │ ├── analytics.js │ └── setup.js ├── services/ │ ├── gmail.js │ ├── tickets.js # Auto-close, reminders, auto-unclaim, naming helpers │ ├── channelQueue.js # enqueueRename / enqueueMove │ ├── staffChannel.js # Legacy mirror helpers (unused in current claim flow) │ ├── staffNotifications.js # Reply alerts + unclaimed reminders │ ├── staffSettings.js # notifydm prefs │ ├── guildSettings.js │ └── debugLog.js ├── routes/ # Optional Express `/api` routes ├── api/ # Bot client accessor for HTTP layer ├── scripts/ # Maintenance / one-off utilities ├── docs/ # Deeper guides (setup, security, MongoDB, API notes) ├── Dockerfile ├── docker-compose.yml ├── package.json └── .env.example / .env.test.example ``` --- ## Database collections | Model / collection | Role | |--------------------|------| | **Ticket** | Gmail thread id, Discord channel/thread id, status, priority, claim (`claimedBy` display label, `claimerId`), legacy `staffChannelId`, escalation tier, `welcomeMessageId`, `ticketTag`, `unclaimedReminderssent`, etc. | | **TicketCounter** | Per-sender local counters (legacy paths). | | **Transcript** | Links closed tickets to transcript message IDs. | | **Tag** | Saved response name + content. | | **GuildSettings** | e.g. `emailRouting`: `thread` \| `category`. | | **StaffSettings** | Per-user `notifyDm` (+ `guildId`, `updatedAt`). | | **StaffNotification** | Per-user `channelId`, `cooldownHours` for reply/unclaimed alerts. | | **CloseRequest** | Pending close workflow if used. | | **User**, **Host**, **DashboardMetrics**, **ErrorLog** | Shared / website-era schemas in the same `models.js` file. | --- ## HTTP: healthcheck & optional API - **`GET /`** → plain text **`Active`** (intended for load balancers / Docker `HEALTHCHECK`). - **`/api/*`** is registered **only after** the bot is `ready` and the optional HTTP API is enabled via env (see `.env.example`). JSON body parsing is enabled; auth uses a Bearer token from configuration. Route definitions live under `routes/` in this repo. --- ## Gmail OAuth refresh token ```bash node get-refresh-token.js ``` Requires `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` in `.env`, and redirect URI **`http://localhost:3000/oauth2callback`** registered on the Google OAuth client. Paste the printed refresh token into `.env` as `REFRESH_TOKEN`. --- ## Documentation in `docs/` Index: **[docs/README.md](docs/README.md)**. Highlights: | Doc | Topic | |-----|--------| | [ENV_AND_SECURITY.md](docs/setup/ENV_AND_SECURITY.md) | Secrets, test env, agent rules | | [MONGODB_SETUP.md](docs/setup/MONGODB_SETUP.md) | Database | | [QUICKSTART.md](docs/setup/QUICKSTART.md) | First-time orientation | | [PROJECT_STRUCTURE.md](docs/setup/PROJECT_STRUCTURE.md) | Layout (may overlap this README) | | [1PASSWORD.md](docs/setup/1PASSWORD.md) | 1Password CLI for `npm run start:1p` | --- ## Development & CI This repo includes [`.gitlab-ci.yml`](.gitlab-ci.yml) with GitLab **SAST** and **secret detection** templates. Adjust or extend stages in GitLab as needed for your fork. --- ## Troubleshooting | Symptom | Checks | |---------|--------| | **Commands missing** | Correct `DISCORD_APPLICATION_ID` + `DISCORD_GUILD_ID`; **restart** bot; Discord can take time to sync. | | **Gmail not ingesting** | `REFRESH_TOKEN`, API enablement, inbox auth; regenerate token if revoked. | | **MongoDB errors** | `MONGODB_URI`, `npm run test-mongodb`. | | **Channels not creating** | Bot **Manage Channels** in ticket categories; category not full (50) unless overflow set. | | **Modal / button no response** | Intents + permissions; bot online; check `DEBUGGING_CHANNEL_ID` / console. | | **Renames “too quickly”** | Discord rename cooldown; wait for channel queue / timestamp in bot message. | | **Test script env on Windows** | `npm run start:test` sets `ENV_FILE` Unix-style; use PowerShell `ENV_FILE` + `node` if the script fails. | --- ## References | Technology | Link | |------------|------| | discord.js v14 | [discord.js guide](https://discordjs.guide/) | | Google APIs (Gmail) | [googleapis Node](https://github.com/googleapis/google-api-nodejs-client) | | Mongoose | [mongoosejs.com](https://mongoosejs.com/) | | Express | [expressjs.com](https://expressjs.com/) | --- ## License ISC