cleanup and simplify
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
120
HOWITWORKS.md
Normal file
120
HOWITWORKS.md
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
# How it works
|
||||||
|
|
||||||
|
Broccolini Bot is a single Node.js process. It does three things at once:
|
||||||
|
|
||||||
|
1. **Listens to Discord** — slash commands, button clicks, modals, ticket-channel messages.
|
||||||
|
2. **Polls Gmail** — every N seconds, pulls unread `category:primary` mail and turns each thread into a Discord ticket channel.
|
||||||
|
3. **Serves a couple of HTTP endpoints** — a public healthcheck and an internal config/control API.
|
||||||
|
|
||||||
|
State lives in MongoDB via Mongoose. There is no queue/worker tier and no public REST API.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Startup
|
||||||
|
|
||||||
|
`broccolini-discord.js` is the entry point. The order matters:
|
||||||
|
|
||||||
|
1. **Module load** — env validation, Discord `Client` is constructed, `interactionCreate` and `messageCreate` listeners are registered, `client.login()` is called.
|
||||||
|
2. The **public Express app** is defined at module scope. It has a 503 gate — any `/api/*` request before the bot is ready returns 503. (No `/api/*` routes are mounted in the MVP, so it's dormant.)
|
||||||
|
3. The **internal Express app** binds at module load on `INTERNAL_API_PORT` (`0.0.0.0` inside the container, not host-published). Reachable only from peers on the `broccoli-net` Docker network. Auth header: `x-internal-secret`.
|
||||||
|
4. **`client.once('ready')`** — once Discord finishes its handshake the bot connects MongoDB, starts the public HTTP listener, registers slash commands with Discord's REST API, then starts the Gmail poll plus optional `setInterval`s for auto-close, auto-unclaim, sweeping orphan ticket channels, and a 6-hour Tickets sweep.
|
||||||
|
|
||||||
|
Every `setInterval` in the `ready` block is wrapped through `trackInterval(...)` into a module-level `Set`. `handleShutdown` (SIGTERM/SIGINT) clears all of them, closes both HTTP servers, calls `client.destroy()`, then closes Mongo.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Email → Discord (the main flow)
|
||||||
|
|
||||||
|
`gmail-poll.js`:
|
||||||
|
|
||||||
|
1. Lists unread messages in `category:primary`.
|
||||||
|
2. For each thread, looks up an existing `Ticket` by `gmailThreadId`. If none, creates a Discord channel under `TICKET_CATEGORY_ID` (or an overflow category if the main is full at Discord's 50-channel limit) and inserts a `Ticket` document.
|
||||||
|
3. Posts a welcome embed + action row (Close / Claim / Escalate) into the channel and pings `ROLE_ID_TO_PING`.
|
||||||
|
4. On subsequent emails in the same thread, just appends the new message to the existing channel.
|
||||||
|
|
||||||
|
**Auth failure halts polling.** On `invalid_grant` / `unauthorized` / 401, `pollSuspended` flips to true, the poll interval is cleared, and the admin is DM'd once. The bot does not retry — fix the token and restart the container.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Discord → Gmail (replies)
|
||||||
|
|
||||||
|
`handlers/messages.js` handles `messageCreate` in ticket channels:
|
||||||
|
|
||||||
|
- If the ticket's `gmailThreadId` starts with `discord-` or `discord-msg-`, it's a Discord-only ticket — skip Gmail entirely.
|
||||||
|
- Otherwise, the staff message is forwarded to the customer via Gmail (threaded reply) using `services/gmail.js`. The staffer's per-user signature (`StaffSignature`) is appended.
|
||||||
|
|
||||||
|
Customer replies coming back via email are picked up by the next Gmail poll and appended to the existing ticket channel.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Discord-only tickets
|
||||||
|
|
||||||
|
Two paths create them:
|
||||||
|
|
||||||
|
- **`/panel`** posts an "Open ticket" button. Clicking it opens a modal asking for email, game, and description. The bot creates a channel and a `Ticket` with a synthetic `gmailThreadId` like `discord-<channelId>`.
|
||||||
|
- **Context menu "Create Ticket From Message"** does the same, prefilled from the source message (`gmailThreadId` like `discord-msg-<msgId>`).
|
||||||
|
|
||||||
|
Replies in these channels stay in Discord. No Gmail traffic.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Channel rename / move queue
|
||||||
|
|
||||||
|
Discord rate-limits channel renames at **2 per 10 minutes per channel**. All channel ops route through `services/channelQueue.js`:
|
||||||
|
|
||||||
|
- `enqueueRename`, `enqueueSend`, `enqueueMove`, `enqueueDelete` — per-channel chained promises. Delete waits for both rename and send tails to drain.
|
||||||
|
- Renames go through `utils/renamer.js`, which uses the `RENAMER_BOT` secondary token. On 401/403/429 from the secondary, the queue falls back to the primary bot's `channel.setName`.
|
||||||
|
- Bypass sites (direct `channel.send` / `setName`) are tagged `// TODO(queue-migrate):` — grep to find them; they get migrated incrementally when touched.
|
||||||
|
|
||||||
|
Logging helpers in `services/debugLog.js` are **fire-and-forget** — `.catch(() => {})`, never `await`. They post to the configured log channels (system, automation, error, gmail, etc.).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## HTTP surfaces
|
||||||
|
|
||||||
|
**Public** (`app`, port `CONFIG.PORT`, default `5000`):
|
||||||
|
- `GET /` → `Active` once ready, `Starting` before. Used by Docker `HEALTHCHECK`.
|
||||||
|
- `/api/*` is gated behind `appReady` and currently unmounted.
|
||||||
|
|
||||||
|
**Internal** (`internalApp`, `INTERNAL_API_PORT`, `broccoli-net` only, header auth):
|
||||||
|
- `GET /config`, `POST /config` — read/write a strict allowlist (`ALLOWED_CONFIG_KEYS` in `services/configSchema.js`). Unknown keys → 400.
|
||||||
|
- `GET /discord/guild` — basic guild info for the settings UI.
|
||||||
|
- `POST /restart`, `GET /restart/status` — exits the process so the container supervisor restarts it.
|
||||||
|
- `POST /gmail/reload` — reloads the Gmail client after credential changes.
|
||||||
|
|
||||||
|
`.env` writes go through `services/configPersistence.js`, which stores values in **backtick** containers because dotenv v17 only decodes `\n`/`\r` inside double-quoted strings.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Storage
|
||||||
|
|
||||||
|
MongoDB, one database, accessed via Mongoose. Models live in `models.js`:
|
||||||
|
|
||||||
|
| Collection | What it stores |
|
||||||
|
|------------|----------------|
|
||||||
|
| `Ticket` | One per email thread or Discord-only ticket. Tracks `gmailThreadId`, `discordThreadId`, status, claimer, priority, escalation tier, ticket tag, last activity, etc. |
|
||||||
|
| `TicketCounter` | Per-sender local counter for ticket numbering. |
|
||||||
|
| `Transcript` | Closed ticket → transcript message pointer. |
|
||||||
|
| `Tag` | Saved-response templates (`/response`). |
|
||||||
|
| `StaffSettings` | Per-user `notifyDm` preference. |
|
||||||
|
| `StaffSignature` | Per-user email signature (valediction, display name, tagline). |
|
||||||
|
|
||||||
|
The `Ticket` schema indexes `{gmailThreadId}` (unique), `{status, lastActivity}`, `{senderEmail, status}`, `{discordThreadId}`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Background jobs (in `ready`)
|
||||||
|
|
||||||
|
| Job | Cadence | Toggle |
|
||||||
|
|-----|---------|--------|
|
||||||
|
| Gmail poll | `GMAIL_POLL_INTERVAL_SECONDS` (default 30s; runtime-tunable via `/gmailpoll`) | always |
|
||||||
|
| `checkAutoClose` | configurable | `AUTO_CLOSE_ENABLED` |
|
||||||
|
| `checkAutoUnclaim` | configurable | `AUTO_UNCLAIM_ENABLED` |
|
||||||
|
| `reconcileDeletedTicketChannels` | hourly + on startup | always |
|
||||||
|
| `services/tickets.startTicketsSweeps` | 6h, `.unref()`-ed | always |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Settings UI (separate process)
|
||||||
|
|
||||||
|
`settings-site/` is its own small Express app with its own `package.json`, `Dockerfile`, and `CLAUDE.md`. It serves a password-protected dashboard that POSTs config changes to this bot's internal API using `INTERNAL_API_SECRET`. Any change to `ALLOWED_CONFIG_KEYS` here can break the UI there — keep them in sync.
|
||||||
555
README.md
555
README.md
@@ -1,521 +1,90 @@
|
|||||||
# Broccolini Bot
|
# 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.
|
A Node.js Discord bot that bridges **Gmail ↔ Discord** for support ticketing, with **MongoDB** for state. Built for Indifferent Broccoli (game-server hosting).
|
||||||
|
|
||||||
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).
|
- Inbound email → Discord ticket channel.
|
||||||
|
- Staff messages in that channel → Gmail reply (threaded).
|
||||||
|
- Discord-originated tickets (panel / context menu) live entirely in Discord.
|
||||||
|
|
||||||
**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)
|
For an architectural overview, see [HOWITWORKS.md](HOWITWORKS.md). For agent/contributor conventions, see [CLAUDE.md](CLAUDE.md).
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 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
|
## Quick start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone <your-repo-url>
|
git clone <repo-url>
|
||||||
cd broccolini-bot
|
cd broccolini-bot
|
||||||
npm install
|
npm install
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
|
# fill DISCORD_TOKEN, DISCORD_APPLICATION_ID, DISCORD_GUILD_ID, TICKET_CATEGORY_ID,
|
||||||
|
# ROLE_ID_TO_PING, MONGODB_URI, GOOGLE_CLIENT_ID/SECRET, REFRESH_TOKEN, MY_EMAIL
|
||||||
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Fill **Discord** (`DISCORD_TOKEN` or `DISCORD_BOT_TOKEN`, `DISCORD_APPLICATION_ID`, `DISCORD_GUILD_ID`, categories, `ROLE_ID_TO_PING`, transcript/log channels).
|
Need a Gmail refresh token? `node get-refresh-token.js` (redirect URI `http://localhost:3000/oauth2callback`). Probe Mongo with `npm run test-mongodb`.
|
||||||
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.
|
Restart the bot after any `.env` change. Restart also re-registers slash commands.
|
||||||
|
|
||||||
---
|
## Deploy (Docker)
|
||||||
|
|
||||||
## Installation
|
```bash
|
||||||
|
docker compose up --build -d
|
||||||
|
docker logs broccolini --tail 50 -f
|
||||||
|
```
|
||||||
|
|
||||||
Same as quick start. Optional:
|
Host port `8892` → container `5000` (`DISCORD_ONLY_PORT`).
|
||||||
|
|
||||||
- **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
|
||||||
|
|
||||||
Configuration is **environment variables** only, loaded in [`config.js`](config.js) as `CONFIG`. Names below match `.env.example` unless noted.
|
All config is environment variables loaded by `config.js` into `CONFIG`. The full list — with descriptions and defaults — lives in [`.env.example`](.env.example). Highlights:
|
||||||
|
|
||||||
### Discord (core)
|
| Variable | Notes |
|
||||||
|
|----------|-------|
|
||||||
| Variable | Required | Description |
|
| `DISCORD_TOKEN` / `DISCORD_BOT_TOKEN` | Bot token. First non-empty after trim wins. |
|
||||||
|----------|----------|-------------|
|
| `DISCORD_APPLICATION_ID`, `DISCORD_GUILD_ID` | Required for slash command registration. |
|
||||||
| `DISCORD_TOKEN` | Yes* | Bot token (*or `DISCORD_BOT_TOKEN`, first non-empty wins after trim). |
|
| `TICKET_CATEGORY_ID` | Default category for email tickets. Validated at startup. |
|
||||||
| `DISCORD_APPLICATION_ID` | Yes | Used as `CLIENT_ID` for REST command registration. |
|
| `DISCORD_TICKET_CATEGORY_ID` | Category for Discord panel/context tickets (falls back to `TICKET_CATEGORY_ID`). |
|
||||||
| `DISCORD_GUILD_ID` | Yes | Guild where slash commands are registered. |
|
| `ROLE_ID_TO_PING` | Support role pinged on new tickets. |
|
||||||
| `TICKET_CATEGORY_ID` | Yes | Default category for **email** tickets (startup validates this). |
|
| `MONGODB_URI` | Mongo connection string. |
|
||||||
| `DISCORD_TICKET_CATEGORY_ID` | No | Category for **Discord** panel/context tickets (falls back to `TICKET_CATEGORY_ID`). |
|
| `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` / `REFRESH_TOKEN` / `MY_EMAIL` | Gmail OAuth + canonical inbox address. |
|
||||||
| `EMAIL_THREAD_CHANNEL_ID` / `DISCORD_THREAD_CHANNEL_ID` | No | Parent **text** channels for thread-style tickets, when used. |
|
| `RENAMER_BOT` | Optional secondary token used for channel renames. |
|
||||||
| `EMAIL_TICKET_OVERFLOW_CATEGORY_IDS` | No | Comma-separated extra categories when the main is full (50 channels). |
|
| `INTERNAL_API_SECRET` / `INTERNAL_API_PORT` | Enable the internal config API used by the settings UI. |
|
||||||
| `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. |
|
## Slash commands
|
||||||
| `ADDITIONAL_STAFF_ROLES` | No | Extra role IDs treated as staff for commands. |
|
|
||||||
| `BLACKLISTED_ROLES` | No | Roles blocked from opening tickets. |
|
| Command | Purpose |
|
||||||
| `TRANSCRIPT_CHANNEL_ID` | No | Where transcripts are posted on close. |
|
|---------|---------|
|
||||||
| `LOGGING_CHANNEL_ID` | No | Ticket lifecycle logs. |
|
| `/escalate`, `/deescalate` | Move ticket between tier 2/3 categories. |
|
||||||
| `DEBUGGING_CHANNEL_ID` | No | Optional error/debug forwarding. |
|
| `/add`, `/remove` | Add/remove user from current ticket channel. |
|
||||||
| `BACKUP_EXPORT_CHANNEL_ID` | No | Target for `/backup` and `/export`. |
|
| `/transfer` | Hand the claim to another staff member. |
|
||||||
| `ACCOUNT_INFO_CHANNEL_ID` | No | Account info flows. |
|
| `/move` | Reparent the channel to another category. |
|
||||||
|
| `/force-close`, `/cancel-close`, `/closetimer` | Force-close flow with cancellable countdown. |
|
||||||
### Escalation categories
|
| `/topic` | Set channel topic. |
|
||||||
|
| `/response` | Saved reply templates (`send`, `create`, `edit`, `delete`, `list`). |
|
||||||
| Variable | Description |
|
| `/panel` | Post an "Open ticket" panel button (thread / category / both). |
|
||||||
|----------|-------------|
|
| `/notifydm` | Toggle DM alerts when a customer replies in your claimed ticket. |
|
||||||
| `EMAIL_ESCALATED_CATEGORY_ID` | Legacy fallback; alias `ESCALATED_CATEGORY_ID`. |
|
| `/signature` | Personal email signature (valediction, display name, tagline). |
|
||||||
| `DISCORD_ESCALATED_CATEGORY_ID` | Discord fallback tier-2–style bucket. |
|
| `/staffthread` | Toggle / configure staff-only threads on tickets. |
|
||||||
| `DISCORD_ESCALATED2_CHANNEL_ID` | Tier **2** placement for Discord tickets (or + fallback category). |
|
| `/pinmessages` | Auto-pin welcome / escalation messages. |
|
||||||
| `EMAIL_ESCALATED2_CHANNEL_ID` | Tier **2** for email tickets (env name says CHANNEL for legacy reasons). |
|
| `/gmailpoll` | Set the Gmail poll interval at runtime. |
|
||||||
| `DISCORD_ESCALATED3_CHANNEL_ID` | Tier **3** Discord. |
|
| `/help` | In-bot summary. |
|
||||||
| `EMAIL_ESCALATED3_CHANNEL_ID` | Tier **3** email. |
|
|
||||||
|
Plus context menus: **Create Ticket From Message**, **View User Tickets**.
|
||||||
Slash `/escalate` and buttons require the appropriate tier IDs for **non-thread** channels (threads skip category moves).
|
|
||||||
|
## Settings UI (optional)
|
||||||
### Staff notifications, claimer display, admin
|
|
||||||
|
`settings-site/` is a separate Express app that talks to the bot's internal config API over the `broccoli-net` Docker network using `INTERNAL_API_SECRET`. It is **not** part of this bot's process. See [`settings-site/CLAUDE.md`](settings-site/CLAUDE.md).
|
||||||
| 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` → `<br>`), 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
|
## Troubleshooting
|
||||||
|
|
||||||
| Symptom | Checks |
|
| Symptom | Check |
|
||||||
|---------|--------|
|
|---------|-------|
|
||||||
| **Commands missing** | Correct `DISCORD_APPLICATION_ID` + `DISCORD_GUILD_ID`; **restart** bot; Discord can take time to sync. |
|
| Slash commands missing | Correct `DISCORD_APPLICATION_ID` + `DISCORD_GUILD_ID`; restart; Discord can take a minute to sync. |
|
||||||
| **Gmail not ingesting** | `REFRESH_TOKEN`, API enablement, inbox auth; regenerate token if revoked. |
|
| Gmail not ingesting | `REFRESH_TOKEN` valid? Auth failure halts polling — re-auth and restart. |
|
||||||
| **MongoDB errors** | `MONGODB_URI`, `npm run test-mongodb`. |
|
| Mongo errors at startup | `MONGODB_URI` reachable? `npm run test-mongodb` to confirm. |
|
||||||
| **Channels not creating** | Bot **Manage Channels** in ticket categories; category not full (50) unless overflow set. |
|
| Channel rename "too quickly" | Discord limit is 2 renames/10 min per channel — the queue serializes; wait it out. |
|
||||||
| **Modal / button no response** | Intents + permissions; bot online; check `DEBUGGING_CHANNEL_ID` / console. |
|
| Modal/button no response | Bot online + intents enabled; check `DEBUGGING_CHANNEL_ID` / container logs. |
|
||||||
| **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
|
## License
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user