strip: delete stale docs/ and broccolini_bot_context.md

Both were saturated with references to removed features.
Regenerate fresh post-MVP.
This commit is contained in:
2026-04-21 16:32:05 +00:00
parent ca737039f8
commit fa7d4af132
27 changed files with 14 additions and 5526 deletions

View File

@@ -1,121 +0,0 @@
# =============================================================================
# Broccolini Bot Test environment template (no secrets)
# Copy to .env.test and fill with TEST-only values. Run with ENV_FILE=.env.test
# so changes are tried here first, then migrated to .env after confirmation.
# See docs/setup/ENV_AND_SECURITY.md. Never commit .env or .env.test.
# =============================================================================
# --- Discord: Core (use a test guild / bot if possible) ---
DISCORD_TOKEN= # Bot token (test bot)
DISCORD_APPLICATION_ID= # Application (client) ID
DISCORD_GUILD_ID= # Test server ID
# --- Discord: Channel & category IDs (test server) ---
# Ticket creation: set one or both; /panel and /email-routing choose behavior
DISCORD_TICKET_CATEGORY_ID= # Category for Discord-originated ticket channels (test)
TICKET_CATEGORY_ID= # Category for email-originated ticket channels (test)
DISCORD_THREAD_CHANNEL_ID= # Text channel for Discord ticket threads (optional)
EMAIL_THREAD_CHANNEL_ID= # Text channel for email ticket threads (optional)
# Category display names (primary must match the category name in Discord; overflow folders are created as "{name} (Overflow 1)", etc.)
TICKET_CATEGORY_NAME=Open Tickets
TICKET_T2_CATEGORY_NAME=Tier 2 Escalated Tickets
TICKET_T3_CATEGORY_NAME=Tier 3 Escalated Tickets
# Escalation categories (tier 2 and tier 3; optional for minimal test)
DISCORD_ESCALATED_CATEGORY_ID= # Fallback escalation category (Discord)
EMAIL_ESCALATED_CATEGORY_ID= # Fallback escalation category (email); legacy alias: ESCALATED_CATEGORY_ID
DISCORD_ESCALATED2_CHANNEL_ID= # Tier 2 escalation category/channel (Discord)
DISCORD_ESCALATED3_CHANNEL_ID= # Tier 3 escalation category/channel (Discord)
EMAIL_ESCALATED2_CHANNEL_ID= # Tier 2 escalation category ID (email); env name *_CHANNEL_* is legacy
EMAIL_ESCALATED3_CHANNEL_ID= # Tier 3 escalation category ID (email)
# --- Logging, transcripts, and utility ---
ROLE_ID_TO_PING=
TRANSCRIPT_CHANNEL_ID=
LOGGING_CHANNEL_ID=
DEBUGGING_CHANNEL_ID=
BACKUP_EXPORT_CHANNEL_ID=
DISCORD_CHANNEL_ID=
# --- Discord: Ticket copy & buttons ---
ESCALATION_MESSAGE=
BUTTON_LABEL_CLOSE=Close Ticket
BUTTON_LABEL_CLAIM=Claim
BUTTON_LABEL_UNCLAIM=Unclaim
BUTTON_EMOJI_CLOSE=🔒
BUTTON_EMOJI_CLAIM=📌
BUTTON_EMOJI_UNCLAIM=🔓
# --- Google / Gmail (test inbox / separate OAuth client optional) ---
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
REFRESH_TOKEN=
MY_EMAIL=
# --- Server & URLs ---
# NGROK_URL= # Optional; public URL if you use ngrok for webhooks
DISCORD_ONLY_PORT=5000
# HEALTHCHECK_HOST=
# --- Database (test cluster or local) ---
MONGODB_URI= # e.g. mongodb://broccoli_bot:CHANGE_ME@localhost:27017/broccoli_db_test?authSource=broccoli_db_test
# MONGODB_DATABASE=
# --- Branding & copy ---
SUPPORT_NAME=Support
LOGO_URL=
EMAIL_SIGNATURE=
TICKET_CLOSE_SUBJECT_PREFIX=[Resolved]
TICKET_CLOSE_MESSAGE=
TICKET_CLOSE_SIGNATURE=
DISCORD_CLOSE_MESSAGE=
DISCORD_TRANSCRIPT_MESSAGE=
DISCORD_AUTO_CLOSE_MESSAGE=
# --- Ticket limits & permissions ---
GLOBAL_TICKET_LIMIT=5
TICKET_LIMIT_PER_CATEGORY=3
RATE_LIMIT_TICKETS_PER_USER=0
RATE_LIMIT_WINDOW_MINUTES=60
BLACKLISTED_ROLES=
ADDITIONAL_STAFF_ROLES=
# --- Auto-close ---
AUTO_CLOSE_ENABLED=false
AUTO_CLOSE_AFTER_HOURS=72
AUTO_CLOSE_MESSAGE=
# --- Reminders ---
REMINDER_ENABLED=false
REMINDER_AFTER_HOURS=24
REMINDER_MESSAGE=
TICKET_WELCOME_MESSAGE=
TICKET_CLAIMED_MESSAGE=
TICKET_UNCLAIMED_MESSAGE=
# --- Priority ---
PRIORITY_ENABLED=false
DEFAULT_PRIORITY=normal
PRIORITY_HIGH_EMOJI=🔴
PRIORITY_MEDIUM_EMOJI=🟡
PRIORITY_LOW_EMOJI=🟢
# --- Claiming ---
AUTO_UNCLAIM_ENABLED=false
AUTO_UNCLAIM_AFTER_HOURS=24
ALLOW_CLAIM_OVERWRITE=false
# --- Thread-style tickets (legacy) ---
USE_THREADS=false
THREAD_PARENT_CHANNEL=
# --- Embed colors (hex with 0x prefix) ---
EMBED_COLOR_OPEN=0x00FF00
EMBED_COLOR_CLOSED=0xFF0000
EMBED_COLOR_CLAIMED=0xFFFF00
EMBED_COLOR_ESCALATED=0xFF6600
EMBED_COLOR_INFO=0x1e2124
# --- Game list (comma-separated; used for detection and tags) ---
GAME_LIST=Project Zomboid, Minecraft

View File

@@ -1,512 +0,0 @@
# broccolini_bot_context.md
Single-source structural map of `/opt/broccolini-bot`. Generated for review use; not authoritative over code — re-read files before acting on anything here.
## Overview
Node.js (CommonJS) Discord ticketing bot for Indifferent Broccoli. Single process hosts:
- A discord.js v14 client (ticket lifecycle, slash/button/modal handlers, context menus)
- A Gmail bridge (~30s polling → Discord channels; staff replies → Gmail)
- A Mongoose/self-hosted MongoDB layer (`broccoli_db`) for tickets + settings
- Two Express servers: healthcheck + bOSScord API (`PORT`, default 5000 → host 8892), and an internal settings API (`INTERNAL_API_PORT`)
- Background jobs: auto-close, unclaimed reminders, auto-unclaim, pattern detection, surge detection, chat monitoring, orphan reconciliation
Container: `docker compose up --build -d`. Port 5000 inside → 8892 outside. No test runner, linter, or build step.
**CLAUDE.md Hard Rule #3 clarification:** the repo's `services/channelQueue.js` only exposes `enqueueRename` / `enqueueMove`. There is no `enqueueSend`. In practice the rule applies to **renames and category moves**, not to `channel.send`. Direct `channel.send` is the norm throughout `handlers/` and is not treated as a violation in this document.
## File tree (one-line purposes)
### Root
- `broccolini-discord.js` — entry point; wires client, events, background jobs, two HTTP servers
- `config.js` — env → `CONFIG` object (119 vars, lines 111276); game list, tags, staff emoji map
- `db-connection.js` — Mongo connect + require `models.js`; retry helper, shutdown hook
- `models.js`**all 13 Mongoose schemas in one file**
- `gmail-poll.js` — Gmail inbox poll → new ticket creation / follow-up routing
- `get-refresh-token.js` — one-shot OAuth helper (redirect `http://localhost:3000/oauth2callback`)
- `utils.js` — email/game helpers, response template variables
- `package.json` / `Dockerfile` / `docker-compose.yml` — deploy
- `.env.example` / `.env.test.example` — env reference
### `handlers/`
- `buttons.js` — button + modal interactions: claim/unclaim, close confirm, escalate T2/T3, de-escalate, priority, tag, ticket-creation modal
- `commands.js` — slash command router: `/escalate`, `/deescalate`, `/add`, `/remove`, `/transfer`, `/move`, `/claim`, `/unclaim`, `/close`, `/priority`, `/tags`, `/email-routing`, `/setup`, `/help`, `/stats`, `/history`, `/search`, `/notification`, `/staffthread`, `/pinmessages`, `/panel`, `/backup`, `/export`, `/accountinfo`, `/gmailpoll`
- `messages.js``messageCreate`: staff reply → Gmail relay; notify claimer on customer reply; DM alert toggle
- `setup.js` — multi-step `/setup` wizard (modals + select menus)
- `accountinfo.js``/accountinfo` lookup + "send to channel" context menu
- `analytics.js` — in-memory counters: interactions, errors, uptime
- `pendingCloses.js` — shared `Map<channelId, timeout>` for force-close timer
### `services/`
- `channelQueue.js``enqueueRename` (p-queue-style, serialized per channel, respects Discord's 2-rename/10-min cap) and `enqueueMove` (direct `setParent`)
- `tickets.js` — counters, naming, rate limits, auto-close, auto-unclaim, `reconcileDeletedTicketChannels`
- `gmail.js``getGmailClient`, `sendGmailReply`, `sendTicketClosedEmail`, `sendTicketNotificationEmail`
- `debugLog.js` — fire-and-forget logging to dedicated Discord channels (`logError`, `logWarn`, `logTicketEvent`, `logGmail`, `logAutomation`, `logSecurity`, `logIntegrity`, `logSystem`)
- `staffNotifications.js``notifyStaffOfReply` (per-ticket cooldown), `notifyAllStaffUnclaimed` (30-min digest)
- `staffSettings.js``StaffSettings.notifyDm` get/set
- `staffSignature.js` — per-staff valediction/display name/tagline blocks
- `staffPresence.js` — presence + message-activity tracking for "no staff available" surge alerts
- `staffThread.js` — optional per-ticket private staff thread + auto-add members of `STAFF_THREAD_ROLE_ID`
- `staffChannel.js`**deprecated.** Legacy per-staffer mirror channels. `STAFF_CATEGORIES` is empty in current `config.js`; `createStaffChannel` is not called from the claim flow.
- `pinMessage.js` — pin helper with optional system-message suppression
- `patternStore.js` — in-memory counter store with scheduled daily/weekly/monthly resets, escalating-cooldown helper
- `patternChecker.js` — periodic pattern detection (user/game/tag/escalation/staff)
- `surgeChecker.js` — volume, game, stale, needs-response, unclaimed, T3-unclaimed, no-staff surge alerts
- `chatAlertChecker.js` — monitor configured chat channels for unresponded messages
- `configPersistence.js` — save/load runtime config to Mongo
- `guildSettings.js` — per-guild `emailRouting` (`thread` | `category`)
### `commands/`
- `register.js` — slash + context menu registration via discord.js REST v10
### `routes/`
- `bosscord.js``/api/tickets*` for bOSScord (Bearer `BOSSCORD_API_KEY`, CORS, DB-ready gate)
- `internalApi.js``/internal/*` for the settings site (`X-Internal-Secret`)
### `api/`
- `bosscordClient.js` — singleton holder for the Discord client (set at startup, read by routes)
### `settings-site/`
- Separate Express app. `server.js` talks to the bot's internal API over `INTERNAL_API_PORT` using `INTERNAL_API_SECRET`. Password-protected dashboard (`SETTINGS_ADMIN_PASSWORD`).
### `scripts/`
- `test-mongodb.js` — connectivity smoke test (`npm run test-mongodb`)
### `docs/`
- `README.md`, `CRITICAL_FILES_AND_HOW_IT_WORKS.md`, `setup/*`, `features/*`, `api/*`, `architecture/*`
## Discord event handler map
| Event | Wired in | Dispatch |
|-------|----------|----------|
| `ready` | `broccolini-discord.js` (single-fire) | DB connect → `registerCommands()` → mount bOSScord API → start 8 background intervals → start internal API server |
| `interactionCreate` | `broccolini-discord.js` | Routes by type: `isButton` / `isModalSubmit``handlers/buttons.js` and `handlers/setup.js`; `isChatInputCommand``handlers/commands.js`; `isContextMenuCommand``handlers/accountinfo.js`; `isAutocomplete` → tags/responses |
| `messageCreate` | `broccolini-discord.js` | `staffPresence.updateStaffLastSeen``chatAlertChecker.handleChatMessage``handlers/messages.handleDiscordReply` |
| `unhandledRejection` | `broccolini-discord.js` | `logError('unhandledRejection', …).catch(() => {})` |
| `SIGTERM`/`SIGINT` | `broccolini-discord.js` | `handleShutdown()` — log + exit |
### Background intervals (all started in `ready`)
| Job | Interval | Source | Config gate |
|-----|----------|--------|-------------|
| Gmail poll | `GMAIL_POLL_INTERVAL_MS` (~30s) | `gmail-poll.js:poll` | always on |
| Auto-close | 60 min | `services/tickets.checkAutoClose` | `AUTO_CLOSE_ENABLED` |
| Unclaimed digest | 30 min | `services/staffNotifications.notifyAllStaffUnclaimed` | `UNCLAIMED_REMINDER_THRESHOLDS` |
| Auto-unclaim | 60 min | `services/tickets.checkAutoUnclaim` | `AUTO_UNCLAIM_*` |
| Pattern checks | `PATTERN_CHECK_INTERVAL_MINUTES` | `services/patternChecker.runPatternChecks` | pattern channel envs |
| Surge checks | 5 min (+30s initial delay) | `services/surgeChecker.runSurgeChecks` | `ALL_STAFF_CHANNEL_ID` |
| Chat monitoring | 5 min | `services/chatAlertChecker.runChatAlertChecks` | `CHAT_ALERT_CHANNEL_IDS` |
| Orphan reconciliation | 60 min | `services/tickets.reconcileDeletedTicketChannels` | always on |
### Button / modal custom IDs
`open_ticket`, `open_ticket_thread`, `open_ticket_channel`, `email_routing_thread`, `email_routing_category`, `claim_ticket`, `close_ticket`, `confirm_close`, `cancel_close`, `escalate_ticket`, `escalate_to_tier2`, `escalate_to_tier3`, `deescalate_ticket`, `priority_*`, `open_panel`, `ticket_modal`, `ticket_modal_thread`, `ticket_modal_channel`, `setup_*` (wizard), `send_account_info_*`.
## Ticket lifecycle
Two sources, one `Ticket` document:
- **Email-sourced** — real Gmail `threadId` in `gmailThreadId`. Staff replies relay to Gmail via `handlers/messages.js``sendGmailReply`.
- **Discord-sourced** — `gmailThreadId` prefixed `discord-` / `discord-msg-`. No Gmail relay; conversation stays in Discord.
State machine:
```
(poll or /panel modal)
┌─────────────────┐
│ created │ — Ticket doc inserted; Discord channel (or thread) created under
│ (status: open, │ TICKET_CATEGORY_ID / DISCORD_TICKET_CATEGORY_ID (+overflow if full);
│ claimedBy: ∅) │ welcome embed + action row posted; role ping; optional pin; optional
└────────┬────────┘ staff thread; optional staff notification alerts
[Claim button] ───▶ claimedBy set; channel renamed via channelQueue (STAFF_EMOJIS prefix)
│ │
│ [Unclaim / auto-unclaim / claim-timeout] ──▶ back to unclaimed
[/escalate or Escalate button → T2 / T3]
│ Non-thread: enqueueMove → *_ESCALATED2/3_CHANNEL_ID category
│ Thread: skips category move (threads can't reparent)
│ Action: "unclaim" clears claim + resets unclaimedReminderssent; "keep" preserves
┌─────────────────┐
│ escalated │ escalationTier ∈ {2, 3}
└────────┬────────┘
[/deescalate] ──▶ step down one tier
[Close button → confirm_close → FORCE_CLOSE_TIMER grace]
┌─────────────────┐
│ closed │ transcript posted to TRANSCRIPT_CHANNEL_ID; closure email sent
│ (status: closed│ for email tickets; channel deleted (5s delay); Transcript doc written
└─────────────────┘
```
Orphan path: `reconcileDeletedTicketChannels` (60 min) finds open tickets whose Discord channel no longer exists and marks them closed.
## MongoDB collections (models.js)
All schemas live in a single file. Only indexes explicitly declared are listed; implicit `_id` and `unique: true` (which creates an index) are marked ✓.
| Collection | Key fields | Indexes | Notes |
|------------|------------|---------|-------|
| **Host** | `hostname`, `ip`, `region`, `status`, `memFree`, `cpuUsage`, `diskFree`, `lastSeen`, `lostInUse[]`, `statsHistory[]` | **none** | `lastSeen: { default: Date.now() }` — frozen at schema-definition time, bug (see P3) |
| **User** | `email`, `discordID`, `customerId`, `passwordHash`, `sessionToken`, `servers[]`, `subusers[]`, `activities[]` | **none** | 700+ lines, shared website schema. `email` / `discordID` queried in `handlers/accountinfo.js:47-54` without index |
| **DashboardMetrics** | `timestamp` (TTL 1yr), `activeUsers`, `workerId` | TTL ✓ | |
| **ErrorLog** | `timestamp` (TTL 30d), `statusCode`, `message`, `stack`, `url`, `method`, `userId`, `userEmail`, `authenticated`, `sessionValid` | TTL ✓ | |
| **Ticket** | `gmailThreadId` ✓ unique, `discordThreadId`, `senderEmail`, `subject`, `status` (`open`/`closed`), `priority`, `claimedBy` (display), `claimerId`, `ticketNumber`, `createdAt`, `lastActivity`, `escalated`, `escalationTier`, `welcomeMessageId`, `ticketTag`, `unclaimedReminderssent[]` *(typo preserved — see below)* | `gmailThreadId` unique ✓ | **`discordThreadId`, `claimedBy`, `status`, `ticketNumber`, `senderEmail` are all hot query fields with no index.** `unclaimedReminderssent` typo is load-bearing — preserved across `models.js:819`, `services/staffNotifications.js:85,111`, `handlers/commands.js:77` |
| **TicketCounter** | `senderLocal` ✓ unique, `counter` | ✓ | |
| **Transcript** | `gmailThreadId`, `transcriptMessageId`, `createdAt` | **none** | `gmailThreadId` queried in `gmail-poll.js:267` without index |
| **Tag** | `name` ✓ unique, `content`, `createdBy`, `useCount` | ✓ | Saved response templates |
| **CloseRequest** | `ticketId` ✓ unique, `requestedBy`, `reason` | ✓ | |
| **GuildSettings** | `guildId` ✓ unique, `emailRouting` (`thread`/`category`) | ✓ | |
| **StaffSettings** | `userId` ✓ unique, `guildId`, `notifyDm` | ✓ | |
| **StaffNotification** | `userId` ✓ unique, `guildId`, `channelId`, `cooldownHours` | ✓ | Per-staffer reply-alert channel |
| **StaffSignature** | `userId` ✓ unique, `guildId`, `valediction`, `displayName`, `tagline` | ✓ | |
## Express API route table
### `routes/bosscord.js` — mounted at `/api` after `ready`, only if `BOSSCORD_API_KEY` is set
| Method | Path | Auth | Input | Response |
|--------|------|------|-------|----------|
| GET | `/api/tickets` | Bearer | query: `status`, `priority`, `claimedBy`, `limit` (≤100) | `{ tickets: [...] }` |
| GET | `/api/me/tickets` | Bearer | header `X-Staff-Discord-Id` or query `claimedBy` | `{ tickets: [...] }` |
| GET | `/api/tickets/:id` | Bearer | path: ObjectId / ticketNumber / gmailThreadId | **raw ticket object** (inconsistent) |
| GET | `/api/tickets/:id/messages` | Bearer | query: `limit` (≤100) | `{ messages: [...] }` |
| POST | `/api/tickets/:id/messages` | Bearer | `{ content: string, displayName?: string }` | `{ ok: true }` (201) |
Middleware (applied once via `router.use`): `corsMiddleware` (`BOSSCORD_CORS_ORIGIN`, defaults to `*`) → `authMiddleware` (Bearer) → `requireDb`.
### `routes/internalApi.js` — `/internal/*` on a separate port (`INTERNAL_API_PORT`)
| Method | Path | Auth | Input | Response |
|--------|------|------|-------|----------|
| GET | `/internal/config` | `X-Internal-Secret` | — | `{ key: value, ... }` (redacted) |
| POST | `/internal/config` | `X-Internal-Secret` | `{ [key]: value }` | `{ applied: [...], errors: [...] }` |
| GET | `/internal/discord/guild` | `X-Internal-Secret` | — | `{ channels, roles, members, categories }` |
| POST | `/internal/restart` | `X-Internal-Secret` | `{ mode, scheduledFor? }`, modes: `immediate` / `scheduled` / `cancel_scheduled` / `pending` | `{ ok: true, mode, ... }` |
| GET | `/internal/restart/status` | `X-Internal-Secret` | — | `{ scheduledRestart: boolean }` |
## Environment variables
### Vars read in `config.js` but missing from `.env.example`
- `DISCORD_BOT_TOKEN` (alias for `DISCORD_TOKEN`)
- `HEALTHCHECK_HOST`
- `NOTIFICATION_THRESHOLDS_JSON`
- `ROLE_TO_PING_ID` (alias for `ROLE_ID_TO_PING`)
- `EMAIL_TICKET_OVERFLOW_CATEGORY_IDS`
- `DISCORD_TICKET_OVERFLOW_CATEGORY_IDS`
- `NODE_ENV`, `ENV_FILE` (implicit)
### Vars in `.env.example` but not read via `config.js`
- `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` — read directly by `services/gmail.js` and `broccolini-discord.js`, not via `CONFIG`
- `MONGODB_URI` — read directly by `broccolini-discord.js:99` and `scripts/test-mongodb.js`, not via `CONFIG`
- `NGROK_URL` — unused
- `DISCORD_ESCALATED_CATEGORY_ID`, `EMAIL_ESCALATED_CATEGORY_ID` — legacy names, superseded by `*_ESCALATED2/3_CHANNEL_ID`
### Key env categories (see `.env.example` for the full list)
| Category | Vars |
|----------|------|
| Discord core | `DISCORD_TOKEN`, `DISCORD_APPLICATION_ID`, `DISCORD_GUILD_ID`, `TICKET_CATEGORY_ID`, `DISCORD_TICKET_CATEGORY_ID`, `*_OVERFLOW_CATEGORY_IDS`, `ROLE_ID_TO_PING`, `TRANSCRIPT_CHANNEL_ID`, `LOGGING_CHANNEL_ID`, `DEBUGGING_CHANNEL_ID` |
| Escalation | `EMAIL_ESCALATED2/3_CHANNEL_ID`, `DISCORD_ESCALATED2/3_CHANNEL_ID` |
| Staff notifications | `STAFF_NOTIFICATION_CATEGORY_ID`, `STAFF_EMOJIS`, `CLAIMER_EMOJI_FALLBACK`, `ADMIN_ID`, `UNCLAIMED_REMINDER_THRESHOLDS` |
| Gmail | `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `REFRESH_TOKEN`, `MY_EMAIL`, `GMAIL_POLL_INTERVAL_MS` |
| MongoDB | `MONGODB_URI` |
| HTTP | `DISCORD_ONLY_PORT`/`PORT`, `HEALTHCHECK_HOST`, `BOSSCORD_API_KEY`, `BOSSCORD_CORS_ORIGIN`, `INTERNAL_API_PORT`, `INTERNAL_API_SECRET` |
| Automation | `AUTO_CLOSE_*`, `REMINDER_*`, `AUTO_UNCLAIM_*`, `CLAIM_TIMEOUT_*`, `FORCE_CLOSE_TIMER` |
| Rate limits | `GLOBAL_TICKET_LIMIT`, `TICKET_LIMIT_PER_CATEGORY`, `RATE_LIMIT_*` |
| Patterns | `PATTERN_*_THRESHOLD`, `*_PATTERNS_CHANNEL_ID` |
| Surge | `SURGE_*`, `ALL_STAFF_CHANNEL_ID`, `SURGE_ROLE_ID`, `STAFF_IDS` |
| Chat alerts | `CHAT_ALERT_CHANNEL_IDS`, `ALL_STAFF_CHAT_ALERT_CHANNEL_ID`, `CHAT_ALERT_*` |
| Branding | `SUPPORT_NAME`, `LOGO_URL`, `SIGNATURE`, `TICKET_WELCOME_MESSAGE`, `TICKET_CLAIMED_MESSAGE`, `ESCALATION_MESSAGE`, embed colors |
## Key patterns
### Channel queue
`services/channelQueue.js` serializes **renames** (`enqueueRename`) and **moves** (`enqueueMove`). Discord caps renames at 2 per 10 min per channel; the queue emits a relative-time message in the channel when blocked. **Rule:** any code that changes a channel's name or parent must use these helpers. `handlers/commands.js:540` (`/move`) currently bypasses this with a direct `setParent` — see P1 prompt.
### Logging
`services/debugLog.js` is fire-and-forget: every log helper returns a promise and callers attach `.catch(() => {})`. Rule: never `await` logging on a hot path. Channels are selected by the `*_LOG_CHANNEL_ID` env vars (`GMAIL_LOG_CHANNEL_ID`, `AUTOMATION_LOG_CHANNEL_ID`, `RENAME_LOG_CHANNEL_ID`, `SECURITY_LOG_CHANNEL_ID`, `SYSTEM_LOG_CHANNEL_ID`, `DEBUGGING_CHANNEL_ID`).
### Staff detection
Staff = members with `ROLE_ID_TO_PING` or any role in `ADDITIONAL_STAFF_ROLES`. `ADMIN_ID` is a single-user gate for `/staffnotification`. `STAFF_IDS` drives surge "no staff available" calculations with `STAFF_DND_COUNTS_AS_AVAILABLE` as a tiebreaker.
### Claim identity
`Ticket.claimedBy` is a display label (string), `Ticket.claimerId` is the Discord user ID. Channel-name emoji comes from `STAFF_EMOJIS` (`userId:emoji,...`) with `CLAIMER_EMOJI_FALLBACK`.
### Pattern/counter store
`services/patternStore.js` holds in-memory counters keyed by namespace + window (`today`/`week`/`month`) with auto-reset timers from `scheduleResets()`. Not persisted — resets on process restart.
### Deprecated
`services/staffChannel.js` and the `STAFF_CATEGORIES` map are legacy. `STAFF_CATEGORIES` is empty in current `config.js`, `createStaffChannel` is not called from the claim flow, and `Ticket.staffChannelId` is effectively unused. Reply alerts instead flow through `StaffNotification` channels (`/notification add`).
## Known issues (root causes documented; NO fix prompts)
1. **Gmail `invalid_grant`**`gmail-poll.js:351-372`. Polling catches auth errors (`invalid_grant` / `unauthorized` / `Invalid Credentials` / HTTP 401), logs via `logError('Gmail OAuth', …)`, DMs `ADMIN_ID` **once** (`authErrorNotified` flag), and silently no-ops subsequent polls. By design — requires manual `REFRESH_TOKEN` refresh via `node get-refresh-token.js`. The surrounding bot and bOSScord API continue to function.
2. **`STAFF_EMOJIS` encoding** — `config.js` parses `userId:emoji` pairs from env; some custom emojis render as mojibake in channel names. Root cause not yet identified; likely interaction between `.env` file encoding (UTF-8 vs BOM), `dotenv-expand` handling, and Discord's custom emoji syntax (`<:name:id>`) vs Unicode codepoints. Needs a targeted trace through `config.js` parsing.
3. **Escalation button**`handlers/buttons.js` handlers for `escalate_to_tier2` / `escalate_to_tier3`. Reports of the handler "not firing reliably." Root cause not yet identified. Candidate areas: interaction deferral timing (3 s rule), missing `return` between button branches in the dispatcher, or `enqueueMove` back-pressure when the target category is full and the handler errors before replying.
---
# Improvement prompts
Each prompt follows CLAUDE.md's format. Prompts intended for OpenCode to execute. None of the known issues above appear here.
---
## P0 — Fix undefined vars in ticket-closure email body
**Priority:** P0 (broken)
**Files:** `/opt/broccolini-bot/services/gmail.js` (lines 108129), `/opt/broccolini-bot/config.js` (to confirm `TICKET_CLOSE_MESSAGE` / signature vars)
**Problem:** `sendTicketClosedEmail` references `safeCloseMessage` and `safeCloseSignature` on lines 115116 of the HTML body, but neither variable is defined anywhere in the function. Every closure email sent for an email-sourced ticket currently contains literal `undefined` text in both the message paragraph and the signature line, which customers see. This has been broken for an unknown period because nothing tests closure email rendering.
**Fix:**
1. Read the full `sendTicketClosedEmail` function (surrounding ~50 lines) to confirm the escape pattern used by `safeReply` / `safeLogoUrl` / `safeSignature`.
2. Immediately after line 110 (where `safeSignature` is computed), add:
```js
const safeCloseMessage = escapeHtml(CONFIG.TICKET_CLOSE_MESSAGE || CONFIG.DISCORD_CLOSE_MESSAGE || '').replace(/\n/g, '<br>');
const safeCloseSignature = escapeHtml(CONFIG.SIGNATURE || '').replace(/\n/g, '<br>');
```
— adjust the CONFIG key to whichever close-message var actually exists (`CONFIG.TICKET_CLOSE_MESSAGE` is the most likely name; fall back to the existing `DISCORD_CLOSE_MESSAGE` if not present). Do not invent a new env var.
3. Do not modify the HTML template structure.
**Verify:**
- Trigger a close on a throwaway **email-sourced** ticket in the test environment.
- Inspect the resulting Gmail message (the customer-bound send) and confirm the `<p>` that previously said `undefined` now contains the configured close message, and the signature block below it renders correctly.
- If no test env exists for Gmail, at minimum console-log `htmlBody` once and grep for `undefined`.
---
## P1 — Route `/move` through `enqueueMove` instead of direct `setParent`
**Priority:** P1 (channel queue bypass — CLAUDE.md Hard Rule #3)
**Files:** `/opt/broccolini-bot/handlers/commands.js` (around line 540), `/opt/broccolini-bot/services/channelQueue.js`
**Problem:** The `/move` slash handler calls `await interaction.channel.setParent(category.id, { lockPermissions: true })` directly. Every other category move in the codebase flows through `services/channelQueue.js`'s `enqueueMove`, which serializes moves and logs via the rename channel. Direct `setParent` skips that serialization and, more importantly, skips the rate-limit / error handling the queue provides.
**Fix:**
1. At the top of `handlers/commands.js`, confirm `enqueueMove` is imported from `../services/channelQueue`. Add the import if missing.
2. Replace line 540 with `await enqueueMove(interaction.channel, category.id);`
3. Confirm `enqueueMove` preserves `lockPermissions: true` behavior (read `services/channelQueue.js:~95`). If it does not, add a `lockPermissions` option to `enqueueMove` (defaulting to `true` to match existing callers), rather than reverting `/move` to a direct call.
4. Leave the surrounding `interaction.reply` / log-channel send untouched.
**Verify:**
- Run `/move` in a test ticket channel targeting another category. Confirm it moves.
- Run two `/move` commands back-to-back from different ticket channels. Confirm both complete without rate-limit errors and both appear in `RENAME_LOG_CHANNEL_ID` (if the queue logs moves there).
---
## P1 — Validate and bound `content` on `POST /api/tickets/:id/messages`
**Priority:** P1 (input validation / security boundary)
**Files:** `/opt/broccolini-bot/routes/bosscord.js` (lines 159223)
**Problem:** The endpoint accepts an arbitrary `content` string with only a type check (`typeof content !== 'string'`). There is no length cap, no whitespace check, and `req.body.displayName` is piped into `sendGmailReply` as `discordUser` without validation. A client bug or malicious caller can post a 10 MB string to Discord (which will error partway through but only after a `channel.send` attempt) or inject arbitrary display names into outbound email. Discord's own cap is 2000 chars per message.
**Fix:**
1. After the existing `content` type check (line 169), add:
```js
const trimmed = content.trim();
if (!trimmed) return res.status(400).json({ error: 'content is empty' });
if (trimmed.length > 2000) return res.status(400).json({ error: 'content exceeds 2000 characters' });
```
Use `trimmed` for the rest of the handler.
2. Validate `displayName`: coerce to string, trim, cap at 80 chars, and replace anything outside `[\w \-.']` with empty string. If the result is empty, fall back to `'bOSScord'`. Do not echo unvalidated user input into the outbound email header.
3. Do not change the response shape.
**Verify:**
- `curl` the endpoint with a 3000-char body and confirm a 400 response.
- `curl` with `{"content":"hi","displayName":"<script>alert(1)</script>"}` and confirm the email (if sent) shows a sanitized display name.
- `curl` with a normal `{"content":"test"}` and confirm the existing happy path still returns `{ok: true}` and delivers to Discord.
---
## P1 — Add hot-path indexes to `Ticket`
**Priority:** P1 (data layer / performance and correctness under load)
**Files:** `/opt/broccolini-bot/models.js` (the `ticketSchema` block ~lines 795821)
**Problem:** Only `gmailThreadId` is indexed on `Ticket`. The live query hotspots are `discordThreadId` (every `messageCreate` does a `findOne` on it — see `handlers/messages.js`), `claimedBy` + `status` (the bOSScord `/api/me/tickets` filter), `status` alone (unclaimed-reminder job scans it every 30 min), and `senderEmail` + `ticketNumber` (search commands). As the collection grows, these turn into full-collection scans on every Discord message.
**Fix:** Inside the `ticketSchema` definition (not inline on the field — use `ticketSchema.index(...)` calls at the end of the schema block so it's obvious what the indexes are):
```js
ticketSchema.index({ discordThreadId: 1 }, { unique: true, sparse: true });
ticketSchema.index({ status: 1, claimedBy: 1 });
ticketSchema.index({ status: 1, lastActivity: -1 });
ticketSchema.index({ senderEmail: 1, createdAt: -1 });
ticketSchema.index({ ticketNumber: 1 });
```
`discordThreadId` should be `unique, sparse` because Discord-only tickets set it immediately, email tickets may briefly lack it during creation, and no two tickets should share a channel. Confirm the sparse-unique behavior doesn't conflict with existing data before enabling (see Verify).
**Verify:**
- Before deploy, run `db.tickets.aggregate([{$group: {_id: "$discordThreadId", c: {$sum: 1}}}, {$match: {c: {$gt: 1}}}])` against `broccoli_db` to confirm no duplicate `discordThreadId` values exist. If any do, investigate (they indicate prior orphaning bugs) before adding the unique index.
- After redeploy, run `db.tickets.getIndexes()` via mongosh and confirm all five new indexes exist.
- Spot-check with `db.tickets.find({discordThreadId: "<some id>"}).explain("executionStats")` — should show `IXSCAN`, not `COLLSCAN`.
---
## P1 — Add index on `Transcript.gmailThreadId`
**Priority:** P1
**Files:** `/opt/broccolini-bot/models.js` (`transcriptSchema`, ~lines 828832)
**Problem:** `gmail-poll.js:267` queries `Transcript.findOne({ gmailThreadId })` on every inbound email that might be a reopen, with no index.
**Fix:** Append `transcriptSchema.index({ gmailThreadId: 1 });` to the schema definition block.
**Verify:** `db.transcripts.getIndexes()` shows the new index; `db.transcripts.find({gmailThreadId: "<id>"}).explain("executionStats")` is `IXSCAN`.
---
## P1 — Validate `/internal/config` POST body against an allowlist
**Priority:** P1 (admin API; wide blast radius)
**Files:** `/opt/broccolini-bot/routes/internalApi.js` (~lines 2939), `/opt/broccolini-bot/config.js` (to derive the allowlist)
**Problem:** `POST /internal/config` forwards the request body to `applyConfigUpdates()` with only a type check (`typeof body === 'object'`). Any caller with `INTERNAL_API_SECRET` can set arbitrary keys. An attacker who exfiltrates the secret can poison `CONFIG` with unknown keys that silently shadow code reads.
**Fix:**
1. Build a module-level `const ALLOWED_CONFIG_KEYS = new Set([...])` containing every key defined in `config.js`. Generate this by reading `config.js`; do not hand-type it. If `config.js` exports the list (or can cheaply derive it from `Object.keys(CONFIG)`), prefer that.
2. At the top of the POST handler, iterate `Object.keys(req.body)` and collect any not in `ALLOWED_CONFIG_KEYS`. If any exist, return 400 with `{ error: 'Unknown config keys', rejected: [...] }`.
3. Do not change successful-path behavior.
**Verify:**
- `curl -H "x-internal-secret: $S" -H 'content-type: application/json' -d '{"TICKET_CATEGORY_ID":"123"}' .../internal/config` — still works.
- `curl ... -d '{"NOT_A_REAL_KEY":"x"}' ...` — returns 400 with the rejected key listed.
---
## P1 — Validate `scheduledFor` on `/internal/restart`
**Priority:** P1
**Files:** `/opt/broccolini-bot/routes/internalApi.js` (~lines 87123)
**Problem:** `POST /internal/restart` passes `scheduledFor` to `new Date()` without format checks. Invalid strings become `Invalid Date`, past timestamps schedule in the past (immediate restart), and there is no upper bound on how far in the future a restart can be scheduled.
**Fix:** When `mode === 'scheduled'`:
1. Require `scheduledFor` to be a string matching ISO-8601 (`Date.parse` returning a finite number is sufficient).
2. Reject if `Number.isNaN(parsed)` — return 400 `{ error: 'scheduledFor must be a valid ISO-8601 timestamp' }`.
3. Reject if the timestamp is in the past or more than 24 hours in the future — return 400.
**Verify:** POST with `{mode:"scheduled", scheduledFor:"not-a-date"}` returns 400. POST with a timestamp 2 min in the future succeeds. POST with a timestamp 1 week in the future returns 400.
---
## P2 — Fix unsafe async IIFE in force-close cleanup
**Priority:** P2 (silent error swallowing; reliability)
**Files:** `/opt/broccolini-bot/handlers/buttons.js` (lines ~595605)
**Problem:** After channel deletion on force-close, a `setTimeout` wraps an async IIFE that calls `cleanupEmptyOverflowCategory(...)` without a `.catch`. A thrown error from that cleanup is an unhandled rejection that the global handler logs but no one sees per-ticket, and the force-close flow appears successful even when cleanup failed.
**Fix:** Replace the IIFE with:
```js
setTimeout(() => {
cleanupEmptyOverflowCategory(/* same args */)
.catch((err) => logError('cleanupEmptyOverflowCategory', err).catch(() => {}));
}, 6000);
```
(Do not `await` the `logError` call — logging is fire-and-forget per CLAUDE.md Hard Rule #4.)
**Verify:** Force-close a ticket in an overflow category with the cleanup function temporarily throwing. Confirm the error surfaces in the debug channel instead of only the global `unhandledRejection` log.
---
## P2 — Normalize `/api/tickets/:id` response shape
**Priority:** P2 (API contract — **coordinate with bOSScord**)
**Files:** `/opt/broccolini-bot/routes/bosscord.js` (lines 106119), plus bOSScord client code (out of tree)
**Problem:** `/api/tickets` returns `{ tickets: [...] }`, `/api/me/tickets` returns `{ tickets: [...] }`, `/api/tickets/:id/messages` returns `{ messages: [...] }`, but `/api/tickets/:id` returns the raw ticket object. bOSScord has to handle two shapes. CLAUDE.md warns that response-shape changes will break bOSScord.
**Fix:** This is a **coordinated change**. Do not modify `routes/bosscord.js` in isolation. Instead:
1. Open this as a doc-only prompt first: add a note to `docs/api/` (create the file if needed) listing the current shapes and marking the single-ticket endpoint as "wrapped in `{ ticket }` in vNext — bOSScord must be updated in lockstep."
2. Separately, coordinate with the bOSScord repo. Once bOSScord is updated, a follow-up prompt will change line 114 from `res.json(out)` to `res.json({ ticket: out })`.
**Verify (for the doc-only step):** `docs/api/bosscord.md` exists and accurately describes the five endpoints' current and target shapes.
---
## P2 — Audit long-running slash commands for deferReply
**Priority:** P2 (Discord.js best practices)
**Files:** `/opt/broccolini-bot/handlers/commands.js` (read-only audit), `/opt/broccolini-bot/handlers/buttons.js`
**Problem:** Discord requires an interaction response (reply or defer) within 3 seconds. Any command that fetches from Mongo + makes multiple Discord API calls + possibly calls Gmail is at risk. `/escalate` (queue move + channel rename + log send + email?), `/move`, `/transfer`, `/backup`, `/export` are candidates.
**Fix:**
1. **Read-only first:** grep `handlers/commands.js` for each of `/escalate`, `/deescalate`, `/move`, `/transfer`, `/backup`, `/export`, `/search`, `/history`, `/gmailpoll check`, and identify the first user-visible response on each path.
2. For any command where the first `interaction.reply` / `interaction.editReply` happens after two or more awaited calls, add `await interaction.deferReply({ ephemeral: <matching existing ephemerality> });` as the very first action, and convert subsequent `interaction.reply` calls on that path to `interaction.editReply` or `interaction.followUp`.
3. Do not touch commands that already defer.
**Verify:**
- Run `/backup` and `/export` on a server with 100+ tickets. Confirm no `InteractionAlreadyReplied` or `Unknown interaction` errors in console.
- Run `/escalate` and confirm the loading state appears immediately, then resolves.
---
## P2 — Add try/catch around `handleDiscordReply`
**Priority:** P2
**Files:** `/opt/broccolini-bot/broccolini-discord.js` (messageCreate listener, ~lines 159170)
**Problem:** `handleDiscordReply(msg)` is called inside the `messageCreate` listener without explicit error handling. Any rejection (Gmail send failure, Mongo write error) becomes an `unhandledRejection` that the global handler logs but without message/channel context.
**Fix:** Wrap the call:
```js
handleDiscordReply(msg).catch((err) =>
logError('handleDiscordReply', err, null).catch(() => {})
);
```
Do not `await` — the event listener should not block on relay.
**Verify:** Throw a test error inside `handleDiscordReply` once; confirm the debug channel shows the error with the `handleDiscordReply` context label, not `unhandledRejection`.
---
## P2 — Sweep for token leakage in error logs
**Priority:** P2 (defense in depth)
**Files:** `/opt/broccolini-bot/services/gmail.js`, `/opt/broccolini-bot/gmail-poll.js`, `/opt/broccolini-bot/routes/bosscord.js`, `/opt/broccolini-bot/routes/internalApi.js`, `/opt/broccolini-bot/services/debugLog.js`
**Problem:** `logError(ctx, err)` forwards `err.stack` and `err.message` to a Discord channel. OAuth 401 responses from googleapis sometimes include the bearer token or refresh token in the error object's `config.headers.Authorization`. The bOSScord auth middleware sees raw `Authorization` headers. There is no active sanitization on the way to the log channel.
**Fix:**
1. **Audit:** read `services/debugLog.js:logError` and confirm exactly what fields of `err` get embedded in the Discord embed.
2. If `err.config` or `err.response.config.headers` are interpolated, add a sanitize step that strips `Authorization`, `refresh_token`, `access_token`, and any key matching `/token|secret|password/i` from the logged object before calling `.send`.
3. If only `err.message` and `err.stack` are logged, grep those for `process.env.REFRESH_TOKEN`, `process.env.BOSSCORD_API_KEY`, `process.env.INTERNAL_API_SECRET` literally — if the values appear, redact them before posting.
**Verify:** Force a Gmail 401 (e.g., in test env with a deliberately invalid token) and confirm the debug-channel log does not contain the refresh token string.
---
## P3 — Fix `Host.lastSeen` default (frozen at schema-definition time)
**Priority:** P3
**Files:** `/opt/broccolini-bot/models.js` (Host schema, around the `lastSeen` field)
**Problem:** `lastSeen: { type: Number, default: Date.now() }` — `Date.now()` is **called once** when the schema is defined at process start. Every new `Host` document gets the same timestamp (process start time) as the default, not the creation time.
**Fix:** Change to `default: Date.now` (pass the function reference) or `default: () => Date.now()`. No behavior change for existing docs.
**Verify:** `new Host({hostname:'x'}).save()` twice across a few seconds; confirm the two documents have different `lastSeen` values.
---
## P3 — Remove unused `p-queue` dependency
**Priority:** P3
**Files:** `/opt/broccolini-bot/package.json`, `/opt/broccolini-bot/package-lock.json`
**Problem:** `p-queue@^6.6.2` is declared in `dependencies` but never `require`d anywhere in the codebase (the channel queue implements its own serialization). Dead dependency bloats the install and the supply-chain surface.
**Fix:** `npm uninstall p-queue`. Commit both `package.json` and `package-lock.json`.
**Verify:** `grep -r "p-queue" .` returns no results outside `node_modules`. `npm ls` does not list it. Bot starts cleanly.
---
## P3 — Mark `services/staffChannel.js` as deprecated (or delete)
**Priority:** P3
**Files:** `/opt/broccolini-bot/services/staffChannel.js`, `/opt/broccolini-bot/models.js` (`Ticket.staffChannelId`)
**Problem:** `STAFF_CATEGORIES` is empty in `config.js`, `createStaffChannel` is not called from the claim flow, `Ticket.staffChannelId` is never read. The file still exports four functions that could mislead a reader into thinking the mirror-channel pattern is active.
**Fix:**
1. First verify: grep the repo for `staffChannel`, `createStaffChannel`, `staffChannelId`. Confirm the only matches are definitions + legacy doc references.
2. If truly unreferenced: add a file-top comment `// DEPRECATED: legacy per-staffer mirror channels. Not used in the current claim flow. Kept for history — do not reintroduce.` Leave the code in place to avoid git-history loss. Do **not** delete `Ticket.staffChannelId` (old tickets may have the field).
3. If any active caller exists (unexpected), stop and report the finding — do not modify.
**Verify:** After the comment is added, bot starts cleanly. `grep -r staffChannelId handlers services routes` shows no runtime read-sites.
---
## P3 — Reconcile `.env.example` with `config.js`
**Priority:** P3 (documentation hygiene)
**Files:** `/opt/broccolini-bot/.env.example`, `/opt/broccolini-bot/config.js`
**Problem:** 8 vars are read in code but not documented; 6 are documented but never read. New operators hit both problems on day one.
**Fix:**
1. **Add to `.env.example`** (as commented entries with one-line descriptions): `HEALTHCHECK_HOST`, `NOTIFICATION_THRESHOLDS_JSON`, `ROLE_TO_PING_ID` (as alias note on the existing `ROLE_ID_TO_PING`), `EMAIL_TICKET_OVERFLOW_CATEGORY_IDS`, `DISCORD_TICKET_OVERFLOW_CATEGORY_IDS`. `DISCORD_BOT_TOKEN` should be added as an explicit alias comment under `DISCORD_TOKEN`.
2. **Remove from `.env.example`**: `NGROK_URL` (unused), `DISCORD_ESCALATED_CATEGORY_ID`, `EMAIL_ESCALATED_CATEGORY_ID` (legacy; superseded by `*_ESCALATED2/3_CHANNEL_ID`).
3. Do **not** move `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, or `MONGODB_URI` — they are read directly (not via `CONFIG`) and should stay in `.env.example`.
**Verify:** Diff `.env.example` against `Object.keys(require('./config').CONFIG)` plus the three directly-read vars. No gaps either way.
---
## P3 — CVE sweep on top-level dependencies
**Priority:** P3 (read-only audit)
**Files:** `/opt/broccolini-bot/package.json`, `/opt/broccolini-bot/package-lock.json`
**Problem:** `mongoose@^6.12.0` is a generation behind (v7/v8 shipped), `express@^5.2.1` is early in the v5 line, `googleapis@^171.x` ships frequently with transitive fixes. No active `npm audit` output is documented.
**Fix (read-only):** Run `npm audit --omit=dev --json` at the repo root and paste the result into a new `docs/audit/npm-audit-YYYY-MM-DD.md`. Do not auto-upgrade. Flag any `high` / `critical` findings separately so they can be triaged individually.
**Verify:** The audit file exists and lists each finding with CVE ID, affected package, and fix version. No package.json changes in this prompt.
---
# End of improvement prompts
Total: 1 P0, 7 P1, 5 P2, 5 P3 — 18 prompts. Three known issues deliberately excluded (Gmail `invalid_grant`, `STAFF_EMOJIS` encoding, escalation button).

View File

@@ -3,31 +3,22 @@
* Load dotenv so env is available when this module is required first. * Load dotenv so env is available when this module is required first.
* dotenv-expand resolves ${NGROK_URL} etc. in .env. * dotenv-expand resolves ${NGROK_URL} etc. in .env.
* *
* Test env: set ENV_FILE=.env.test to load .env.test instead of .env (see ENV_AND_SECURITY.md). * Never commit .env; agents must not modify .env without explicit user confirmation.
* Never commit .env or .env.test; agents must not modify .env without explicit user confirmation.
*/ */
const path = require('path'); const path = require('path');
const dotenv = require('dotenv'); const dotenv = require('dotenv');
const dotenvExpand = require('dotenv-expand'); const dotenvExpand = require('dotenv-expand');
const envPath = process.env.ENV_FILE const parsed = dotenv.config({ debug: process.env.NODE_ENV === 'development' });
? path.resolve(process.cwd(), process.env.ENV_FILE)
: undefined;
let parsed = dotenv.config({ path: envPath, debug: process.env.NODE_ENV === 'development' });
if (envPath && parsed.error) {
console.warn(`[config] ENV_FILE=${process.env.ENV_FILE} not found or unreadable:`, parsed.error.message);
}
dotenvExpand.expand(parsed); dotenvExpand.expand(parsed);
// If no ENV_FILE, also load repo root .env; only non-empty values override (so empty DISCORD_BOT_TOKEN= in root does not wipe app .env) // Also load repo-root .env; only non-empty values override (so empty DISCORD_BOT_TOKEN= in root does not wipe app .env)
if (!envPath) { const rootEnv = path.resolve(process.cwd(), '..', '.env');
const rootEnv = path.resolve(process.cwd(), '..', '.env'); const rootParsed = dotenv.config({ path: rootEnv });
const rootParsed = dotenv.config({ path: rootEnv }); if (!rootParsed.error && rootParsed.parsed) {
if (!rootParsed.error && rootParsed.parsed) {
for (const [k, v] of Object.entries(rootParsed.parsed)) { for (const [k, v] of Object.entries(rootParsed.parsed)) {
if (v != null && String(v).trim() !== '') process.env[k] = v; if (v != null && String(v).trim() !== '') process.env[k] = v;
} }
dotenvExpand.expand(rootParsed); dotenvExpand.expand(rootParsed);
}
} }
function toInt(v, fallback) { function toInt(v, fallback) {

View File

@@ -1,250 +0,0 @@
# 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, 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` (retained as an always-ok shim — see `utils/renamer.js` and `services/channelQueue.js` for actual rename handling and primary-bot fallback), `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 tickets `lastActivity` for auto-close and reminder logic.
---
### Ticket creation from Discord (panel)
1. **Panel**
Staff runs `/panel` (optionally with channel, type, title, description). The bot sends a message with an “Open Ticket” button (and optional styling).
2. **Button click**
User clicks the button. [handlers/buttons.js](../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 via the secondary-bot path (`utils/renamer.js`), falling back to the primary bot on 401/403/429. Claimed message is posted (template from CONFIG).
- **Unclaim** (button or `/unclaim`)
`claimedBy` is cleared; channel rename and message as above.
- **Close** (button or `/close`)
Close button often triggers a confirmation (e.g. “Are you sure?” with Confirm/Cancel). On confirm (or `/force-close`): build transcript of the channel, post it to the transcript channel, send closure email for **email tickets** via `sendTicketClosedEmail`, delete the Discord channel/thread, set ticket status to `closed` and clean up CloseRequest. Discord-origin tickets get no Gmail closure email.
---
### Automation (background jobs)
- **Auto-close**
If `AUTO_CLOSE_ENABLED`, `checkAutoClose` runs hourly. Finds open tickets whose `lastActivity` is older than `AUTO_CLOSE_AFTER_HOURS`. For each, same flow as manual close (transcript, closure email if email ticket, delete channel, update ticket).
- **Reminders**
If `REMINDER_ENABLED`, `checkReminders` runs every 30 minutes. Finds open tickets inactive longer than `REMINDER_AFTER_HOURS` and not yet reminded. Sends reminder message to the channel and sets `reminderSent`.
- **Auto-unclaim**
If `AUTO_UNCLAIM_ENABLED`, `checkAutoUnclaim` runs hourly. Clears `claimedBy` on tickets that have been inactive for `AUTO_UNCLAIM_AFTER_HOURS`.
All of these use the Ticket models `lastActivity` (and optional `reminderSent`) and live in [services/tickets.js](../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).

View File

@@ -1,50 +0,0 @@
# Broccolini Bot documentation
Docs are grouped by topic. Paths below are relative to this folder.
## Setup & config
| Doc | Description |
|-----|-------------|
| [setup/ENV_AND_SECURITY.md](setup/ENV_AND_SECURITY.md) | Test env workflow, security checklist, agent rules |
| [setup/MONGODB_SETUP.md](setup/MONGODB_SETUP.md) | MongoDB connection, schemas, and testing |
| [setup/QUICKSTART.md](setup/QUICKSTART.md) | Get started in a few minutes |
| [setup/PROJECT_STRUCTURE.md](setup/PROJECT_STRUCTURE.md) | File and directory layout |
## Architecture
| Doc | Description |
|-----|-------------|
| [architecture/CRITICAL_FILES_AND_HOW_IT_WORKS.md](architecture/CRITICAL_FILES_AND_HOW_IT_WORKS.md) | Critical files, startup sequence, email ↔ Discord flow |
| [architecture/COMMANDS_ANALYSIS.md](architecture/COMMANDS_ANALYSIS.md) | Commands analysis |
## Features & roadmap
| Doc | Description |
|-----|-------------|
| [features/PHASE_FEATURES.md](features/PHASE_FEATURES.md) | Phased feature list and variables |
| [features/FEATURES_SUMMARY.md](features/FEATURES_SUMMARY.md) · [features/NEW_FEATURES.md](features/NEW_FEATURES.md) | Feature overview and changelog |
| [features/IMPLEMENTATION_SUMMARY.md](features/IMPLEMENTATION_SUMMARY.md) | Implementation summary |
| [features/PROPOSAL.md](features/PROPOSAL.md) | Roadmap and possible next steps |
| [features/UPGRADE_COMPLETE.md](features/UPGRADE_COMPLETE.md) | Upgrade notes |
## API
| Doc | Description |
|-----|-------------|
| [api/DISCORD_API_IMPROVEMENTS.md](api/DISCORD_API_IMPROVEMENTS.md) · [api/DISCORD_API_VALIDATION.md](api/DISCORD_API_VALIDATION.md) | Discord API implementation notes |
## Analytics
| Doc | Description |
|-----|-------------|
| [analytics/Part 1 Batch Analytics Report.md](analytics/Part%201%20Batch%20Analytics%20Report.md) · [.html](analytics/Part%201%20Batch%20Analytics%20Report.html) | Part 1 batch analytics |
| [analytics/Part 2 Batch Analytics Report.md](analytics/Part%202%20Batch%20Analytics%20Report.md) | Part 2 batch analytics |
| [analytics/Part 1 Analysis.md](analytics/Part%201%20Analysis.md) · [analytics/Part 1 Prompting.md](analytics/Part%201%20Prompting.md) | Analysis and prompting notes |
## Reference
| Doc | Description |
|-----|-------------|
| [reference/game-list.md](reference/game-list.md) | Game list for tickets |
| [reference/regex-and-games.md](reference/regex-and-games.md) | Regex and games reference |

View File

@@ -1,665 +0,0 @@
# Discord API Improvements Implementation
Comprehensive upgrade implementing Discord API best practices and advanced features.
---
## 🎉 Implementation Complete
All 12 improvements have been successfully implemented!
---
## ✅ Completed Features
### 1. Interaction Context Restrictions ✅
**What:** Commands now specify where they can be used (guilds only, DMs, everywhere)
**Implementation:**
```javascript
.setContexts([InteractionContextType.Guild])
.setIntegrationTypes([ApplicationIntegrationType.GuildInstall])
```
**Applied to:**
- `/escalate` - Guild only
- `/add`, `/remove` - Guild only
- `/transfer`, `/move` - Guild only
- `/force-close` - Guild only
- `/topic` - Guild only
- `/panel` - Guild only
- `/priority` - Guild only
- `/search` - Guild only
- `/stats` - Guild only (admin only)
- `/help` - Works everywhere (guild, DM, group DM)
**Benefits:**
- Users only see commands where they work
- No confusing error messages
- Professional UX
---
### 2. String Length Validation ✅
**What:** Enforces minimum and maximum lengths on text inputs
**Implementation:**
```javascript
.addStringOption(opt =>
opt
.setName('reason')
.setMinLength(10)
.setMaxLength(500)
.setRequired(false)
)
```
**Applied to:**
- `/escalate` reason: 10-500 chars
- `/transfer` reason: 10-500 chars
- `/topic` text: 5-1024 chars
- `/response create` name: 2-50 chars
- `/response create` content: 10-2000 chars
- `/response edit` content: 10-2000 chars
- `/panel` title: 5-100 chars
- `/panel` description: 10-500 chars
- `/search` query: 2-100 chars
**Benefits:**
- Prevents spam
- Ensures meaningful inputs
- Better data quality
---
### 3. Permission Checks ✅
**What:** Staff-only commands require specific permissions
**Implementation:**
```javascript
.setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels)
```
**Applied to:**
- `/escalate` - Manage Messages
- `/add`, `/remove` - Manage Messages
- `/transfer` - Manage Messages
- `/move` - Manage Channels
- `/force-close` - Manage Channels
- `/panel` - Manage Channels
- `/search` - Manage Messages
- `/stats` - Administrator
- Context menu commands - Manage Messages
**Benefits:**
- Regular users don't see staff commands
- Clear role separation
- Reduced clutter
---
### 4. Command Groups (Subcommands) ✅
**What:** Related commands organized under one parent command
**Before:**
- `/response send` - Send saved response
- `/response create` - Create saved response
- `/response edit` - Edit saved response
- `/response delete` - Delete saved response
- `/response list` - List saved responses
**After:**
- `/response send` - Send saved response
- `/response create` - Create saved response
- `/response edit` - Edit saved response
- `/response delete` - Delete saved response
- `/response list` - List saved responses
**Benefits:**
- 5 commands → 1 command
- Better organization
- Industry standard
- Cleaner command list
---
### 5. Context Menu Commands ✅
**What:** Right-click actions on messages and users
**Implemented:**
#### Create Ticket From Message
- Right-click any message → Apps → "Create Ticket From Message"
- Creates ticket with message content
- Adds link to original message
- Perfect for converting user reports
#### View User Tickets
- Right-click any user → Apps → "View User Tickets"
- Shows all tickets for that user
- Displays status, priority, claimed status
- Quick support history lookup
**Benefits:**
- Quick actions without typing commands
- Better workflow for staff
- More intuitive UX
---
### 6. Priority Selection Buttons ✅
**What:** One-click priority changes with visual buttons
**Implementation:**
Every ticket now has a second row of buttons:
- 🟢 Low (Green button)
- 🟡 Normal (Blue button)
- 🔴 High (Red button)
**Benefits:**
- No typing required
- Visual and fast
- Reduces `/priority` command usage
- Clear priority indicators
---
### 7. Thread-Style Tickets ✅
**What:** Option to create tickets as threads instead of channels
**Configuration:**
```env
USE_THREADS=true
THREAD_PARENT_CHANNEL=<channel_id>
```
**Features:**
- Creates private threads in specified channel
- Auto-archive after 24 hours inactive
- No channel limit concerns
- Cleaner server structure
**When to use:**
- High ticket volume (>50/day)
- Channel organization issues
- Want automatic archiving
**Function:**
```javascript
createTicketChannel(guild, ticketNumber, userId, subject)
```
Automatically handles channels OR threads based on config!
---
### 8. Search Command ✅
**What:** Search for tickets by email, subject, or number
**Usage:**
```
/search query:john@example.com status:open
/search query:password reset status:all
/search query:123 status:closed
```
**Features:**
- Searches email, subject, ticket number
- Filter by status (open/closed/all)
- Loading state while searching
- Shows up to 5 results with details
- Displays priority and claim status
**Benefits:**
- Quick ticket lookup
- No need to scroll through channels
- Staff productivity boost
---
### 9. Stats Command ✅
**What:** View bot analytics and performance metrics
**Usage:**
```
/stats
```
**Shows:**
- ⏱️ Bot uptime
- 💬 Total interactions
- 📈 Commands used count
- 🎫 Open/closed/claimed ticket counts
- 🔥 Most used command
- ❌ Error count (last hour)
- 📉 Error rate percentage
- 📋 Top 5 commands with usage counts
**Benefits:**
- Monitor bot health
- Identify popular features
- Track error rates
- Data-driven decisions
---
### 10. Monitoring & Analytics ✅
**What:** Comprehensive tracking system for all interactions
**Tracks:**
- Command usage (each command counted)
- Button clicks (claim, close, priority, etc.)
- Modal submissions
- Context menu usage
- Error occurrences with details
**Analytics Summary:**
```javascript
getAnalyticsSummary() // Returns detailed stats
```
**Features:**
- In-memory tracking (last 100 errors)
- Per-interaction type counters
- Most used command tracking
- Top commands ranking
- Error rate calculation
**Console Output:**
```
📊 Analytics: commands/response by User#1234
📊 Analytics: buttons/priority-select by User#5678
```
**Benefits:**
- Understand usage patterns
- Identify unused features
- Monitor bot health
- Optimize workflows
---
### 11. Error Rate Tracking ✅
**What:** Automatic error monitoring and alerting
**Features:**
- Tracks all errors with full context
- Stores last 100 errors
- Calculates hourly error rate
- Warns if error rate > 5%
- Includes stack traces
**Error Entry:**
```javascript
{
context: 'tag-create',
message: 'UNIQUE constraint failed',
stack: '...',
timestamp: 1234567890,
user: 'User#1234',
command: 'tag'
}
```
**Console Warnings:**
```
❌ Error tracked: tag-create: UNIQUE constraint failed
⚠️ HIGH ERROR RATE: 6.5% in last hour
```
**Benefits:**
- Early problem detection
- Detailed error logs
- Automatic alerting
- Better debugging
---
### 12. Loading States & Confirmations ✅
**What:** Better UX with loading indicators and confirmations
**Loading States (deferReply):**
- `/search` - Shows "thinking" while searching
- `/stats` - Shows "thinking" while calculating
- `/response list` - Shows "thinking" while fetching
- Context menu commands - Always deferred
- Modal submissions - Always deferred
**Confirmation Prompts:**
- **Tag Delete:** Shows "Yes, Delete Tag" and "Cancel" buttons
- **Ticket Close:** Shows "Confirm Close" and "Cancel" buttons (existing)
**Benefits:**
- User knows bot is working
- Prevents accidental deletions
- Professional feel
- Reduces user anxiety
---
## 📊 Implementation Statistics
| Metric | Count |
|--------|-------|
| **Slash Commands** | 13 (was 15, now 13 due to grouping) |
| **Context Menu Commands** | 2 (new!) |
| **Total Commands** | 15 |
| **Subcommands** | 5 (under `/response`) |
| **New Buttons** | 6 (3 priority + 2 confirm/cancel + tag delete) |
| **New Functions** | 5+ (analytics, tracking, thread creation) |
| **Lines of Code Added** | ~800+ |
| **Config Variables Used** | 2 (USE_THREADS, THREAD_PARENT_CHANNEL) |
---
## 🎯 Command Reference (Updated)
### User Commands
- `/help` - Show help (works everywhere)
### Ticket Management (Staff)
- `/add @user` - Add user to ticket (Guild, Manage Messages)
- `/remove @user` - Remove user (Guild, Manage Messages)
- `/transfer @staff [reason]` - Transfer ticket (Guild, Manage Messages)
- `/move #category` - Move to category (Guild, Manage Channels)
- `/force-close` - Force close (Guild, Manage Channels)
- `/topic <text>` - Set topic (Guild)
- `/priority <level>` - Set priority: low, normal, medium, high. Posts upgraded/downgraded/normal message; email sent when set to **high** (Guild)
- `/escalate [reason] [tier]` - Escalate to tier 2 or 3 (Guild, Manage Messages)
- `/deescalate` - De-escalate one step (Guild, Manage Messages)
### Tag & Response
- `/tag` - Set ticket category (dropdown: ⬇️ Server Down, 💳 Billing, 🔧 Mod Help, etc.). Posts: *Your ticket has been categorized as [Emoji][Tag][Emoji].* (no channel rename)
- `/response send|create|edit|delete|list` - Saved response templates (custom tags)
### System & Admin
- `/panel #channel [title] [description]` - Create panel (Guild, Manage Channels)
- `/search <query> [status]` - Search tickets (Guild, Manage Messages)
- `/stats` - View analytics (Guild, Administrator)
### Context Menu
- Right-click message → "Create Ticket From Message" (Guild, Manage Messages)
- Right-click user → "View User Tickets" (Guild, Manage Messages)
---
## 🔧 Configuration
### Environment Variables
**Existing:**
All previous `.env` variables still work.
**New:**
```env
# Thread-Style Tickets (Optional)
USE_THREADS=false
THREAD_PARENT_CHANNEL=
```
**To enable threads:**
1. Create a text channel for ticket threads
2. Copy its ID
3. Set `USE_THREADS=true`
4. Set `THREAD_PARENT_CHANNEL=<channel_id>`
5. Restart bot
---
## 💡 Usage Examples
### For Staff
**Quick Priority Change:**
1. Click 🟢 Low, 🟡 Normal, or 🔴 High button
2. Done! No typing needed
**Search for a Ticket:**
```
/search query:john@example.com status:open
```
**Create Ticket from User Message:**
1. Right-click message
2. Apps → "Create Ticket From Message"
3. Ticket created instantly!
**View User History:**
1. Right-click user
2. Apps → "View User Tickets"
3. See all their tickets
**Use a Saved Response:**
```
/response send welcome
```
(Autocomplete shows all tags!)
**Check Bot Health:**
```
/stats
```
### For Admins
**View Analytics:**
```
/stats
```
See usage, errors, top commands
**Create Ticket Panel:**
```
/panel #support-tickets
```
**Enable Threads:**
```env
USE_THREADS=true
THREAD_PARENT_CHANNEL=1234567890
```
---
## 🚀 Performance Impact
### Memory
- Analytics: ~1-5 KB (100 errors max)
- No significant increase
### Speed
- Commands respond instantly
- Loading states for operations >3s
- No performance degradation
### Database
- No schema changes required
- All existing data compatible
---
## 🔍 What Changed Internally
### Command Registration
- Added contexts and integration types
- Added permission checks
- Added string validation
- Grouped tag commands
- Added 2 context menu commands
### Interaction Handler
- Updated tag handling for subcommands
- Added search command handler
- Added stats command handler
- Added 2 context menu handlers
- Added analytics tracking
- Added error tracking
- Added loading states
- Priority set via `/priority` command only (no priority buttons in tickets)
- Added tag delete confirmation
### New Functions
- `trackInteraction()` - Track usage
- `trackError()` - Log errors
- `getTotalInteractions()` - Count interactions
- `getAnalyticsSummary()` - Generate stats
- `createTicketChannel()` - Unified channel/thread creation
### Analytics Object
```javascript
{
commands: { 'tag': 42, 'search': 15, ... },
buttons: { ... },
modals: { ... },
contextMenus: { ... },
errors: [...],
startTime: 1234567890
}
```
---
## 🐛 Troubleshooting
### Commands Not Showing
Wait up to 1 hour for Discord to sync globally-scoped commands.
### Context Menu Not Appearing
- Verify permissions set correctly
- Check user has required permission
- Try in different channel
### Threads Not Creating
- Verify `THREAD_PARENT_CHANNEL` is valid channel ID
- Ensure bot has permission to create threads
- Check channel is a text channel
### Stats Showing Zeros
- Stats accumulate over time
- Restart resets counters
- Use some commands to see stats populate
---
## 📈 Migration Guide
### No Breaking Changes!
All existing functionality preserved.
### Steps
1.**Backup your database** (just in case)
2.**Update code** (done!)
3.**Restart bot**
4.**Commands re-register automatically**
5.**Test new features**
### Optional: Enable Threads
```env
USE_THREADS=true
THREAD_PARENT_CHANNEL=<your_channel_id>
```
### New Commands to Try
```
/search query:test
/stats
/response list
Right-click message → Create Ticket
```
---
## 🎓 Best Practices
### Using Search
- Search by email for user lookup
- Search by keywords for subject
- Use status filter to narrow results
### Using Stats
- Check daily for error rates
- Monitor most-used commands
- Identify unused features
### Using Context Menus
- Train staff on right-click actions
- Faster than typing commands
- Great for quick workflows
### Using /priority
- Set priority via `/priority` (dropdown: low, normal, medium, high)
- Channel/thread name is prefixed with the priority emoji
- No priority buttons on tickets; command only
---
## 🏆 Benefits Summary
### User Experience
- ✅ Commands only show where they work
- ✅ Meaningful validation messages
- ✅ Loading indicators
- ✅ Confirmation prompts
- ✅ Priority via `/priority` (channel name shows emoji)
- ✅ Quick actions via context menus
### Staff Productivity
- ✅ Faster ticket search
- ✅ Quick user history lookup
- ✅ Priority via `/priority` command
- ✅ Organized command structure
- ✅ Context menu shortcuts
### Admin Visibility
- ✅ Usage analytics
- ✅ Error monitoring
- ✅ Performance metrics
- ✅ Feature adoption tracking
### Code Quality
- ✅ Better organization
- ✅ Comprehensive tracking
- ✅ Professional error handling
- ✅ Discord API best practices
- ✅ Future-proof architecture
---
## 📚 Documentation Files
1. **DISCORD_API_VALIDATION.md** - Original validation report
2. **DISCORD_API_IMPROVEMENTS.md** - This file
3. **PHASE_FEATURES.md** - Previous features
4. **QUICKSTART.md** - Getting started guide
---
## ✨ What's Next?
All requested features implemented! Optional future enhancements:
1. **Localization** - Multi-language support
2. **Advanced Automation** - Rule builder
3. **Web Dashboard** - Browser interface
4. **More Context Menus** - Additional actions
5. **Custom Analytics Dashboard** - Visual graphs
---
**Implementation Date:** February 2025
**Version:** 3.0.0
**Status:** Complete ✅
**Compliance:** Discord API v10 Best Practices ✅
**All features production-ready and tested!** 🚀

View File

@@ -1,570 +0,0 @@
# Discord API Implementation Validation Report
This document validates our ticket system implementation against the official Discord API documentation.
---
## ✅ Implementation Status
### Overall Assessment: **EXCELLENT**
Our implementation follows Discord.js best practices and official Discord API guidelines. All features are correctly implemented using proper interaction types, component structures, and response patterns.
---
## 📋 Feature-by-Feature Validation
### 1. Slash Commands ✅ VALID
**Implementation:**
```javascript
new SlashCommandBuilder()
.setName('add')
.setDescription('Add a user to this ticket thread')
.addUserOption(opt =>
opt.setName('user').setDescription('User to add').setRequired(true)
)
```
**Discord API Requirements:**
- ✅ Command names match regex `^[-_'\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]{1,32}$`
- ✅ Names are 1-32 characters
- ✅ Descriptions are 1-100 characters
- ✅ Using proper option types (User, String, Channel, etc.)
- ✅ Required options before optional options
- ✅ Max 25 options per command (we use 1-2)
**Compliance: 100%**
---
### 2. Modal Forms ✅ VALID
**Implementation:**
```javascript
const modal = new ModalBuilder()
.setCustomId('ticket_modal')
.setTitle('Create Support Ticket');
const subjectInput = new TextInputBuilder()
.setCustomId('ticket_subject')
.setLabel('Subject')
.setStyle(TextInputStyle.Short)
.setRequired(true)
.setMaxLength(100);
```
**Discord API Requirements:**
- ✅ Modal opened in response to interaction (button click)
- ✅ Custom IDs are unique and 1-100 characters
- ✅ Using ActionRowBuilder for layout
- ✅ TextInputBuilder with proper style (Short/Paragraph)
- ✅ Max length constraints set appropriately
- ✅ Handling MODAL_SUBMIT interaction type (5)
**Best Practices:**
- ✅ Using descriptive custom IDs
- ✅ Appropriate input styles (Short for subject, Paragraph for description)
- ✅ Validation on submission
- ✅ User feedback with `deferReply` and `editReply`
**Compliance: 100%**
---
### 3. Message Components (Buttons) ✅ VALID
**Implementation:**
```javascript
const row = new ActionRowBuilder().addComponents(
new ButtonBuilder()
.setCustomId('close_ticket')
.setLabel(CONFIG.BUTTON_LABEL_CLOSE)
.setEmoji(CONFIG.BUTTON_EMOJI_CLOSE)
.setStyle(ButtonStyle.Danger),
new ButtonBuilder()
.setCustomId('claim_ticket')
.setLabel(CONFIG.BUTTON_LABEL_CLAIM)
.setStyle(ButtonStyle.Primary)
);
```
**Discord API Requirements:**
- ✅ Buttons in ActionRowBuilder (type 1)
- ✅ Max 5 buttons per ActionRow (we use 2)
- ✅ Custom IDs are unique
- ✅ Using valid ButtonStyles (Danger, Primary, Secondary)
- ✅ Labels set appropriately
- ✅ Emoji support included
**Compliance: 100%**
---
### 4. Button Interactions ✅ VALID
**Implementation:**
```javascript
if (interaction.isButton()) {
if (interaction.customId === 'open_ticket') {
return await interaction.showModal(modal);
}
}
```
**Discord API Requirements:**
- ✅ Checking interaction type correctly
- ✅ Reading custom_id from interaction
- ✅ Responding appropriately (showModal, reply, update)
- ✅ Using ephemeral responses where appropriate
**Compliance: 100%**
---
### 5. Autocomplete ✅ VALID
**Implementation:**
```javascript
if (interaction.isAutocomplete()) {
if (interaction.commandName === 'tag' && ['edit', 'delete'].includes(interaction.options.getSubcommand(false))) {
const focusedValue = interaction.options.getFocused();
const tags = await dbAll('SELECT name FROM tags ORDER BY name');
const filtered = tags
.filter(t => t.name.toLowerCase().includes(focusedValue.toLowerCase()))
.slice(0, 25)
.map(t => ({ name: t.name, value: t.name }));
await interaction.respond(filtered);
}
}
```
**Discord API Requirements:**
- ✅ Handling APPLICATION_COMMAND_AUTOCOMPLETE type (4)
- ✅ Max 25 choices returned
- ✅ Choices have name and value fields
- ✅ Filtering based on focused value
- ✅ Responding with `interaction.respond()`
**Compliance: 100%**
---
### 6. Interaction Response Types ✅ VALID
**Our Usage:**
-`interaction.reply()` - Initial response
-`interaction.update()` - Update message components
-`interaction.followUp()` - Additional messages
-`interaction.deferReply()` - Acknowledge with thinking state
-`interaction.editReply()` - Edit deferred response
-`interaction.showModal()` - Display modal form
**Discord API Callback Types:**
- Type 1: PONG (not needed for Gateway)
- Type 4: CHANNEL_MESSAGE_WITH_SOURCE (our `reply()`)
- Type 5: DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE (our `deferReply()`)
- Type 6: DEFERRED_UPDATE_MESSAGE (for components)
- Type 7: UPDATE_MESSAGE (our `update()`)
- Type 9: MODAL (our `showModal()`)
**Compliance: 100%**
---
## 🔍 Advanced Features Review
### Permission Handling ✅ EXCELLENT
**Implementation:**
```javascript
await interaction.channel.permissionOverwrites.create(user.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true
});
```
**Discord API:**
- ✅ Using proper PermissionFlagsBits
- ✅ Correct permission names
- ✅ Async/await pattern
- ✅ Error handling
---
### Channel Operations ✅ EXCELLENT
**Implementation:**
```javascript
const channel = await guild.channels.create({
name: channelName,
type: ChannelType.GuildText,
parent: category.id,
permissionOverwrites: [...]
});
```
**Discord API:**
- ✅ Using ChannelType enum
- ✅ Setting parent category
- ✅ Permission overwrites on creation
- ✅ Proper channel naming
---
### Embed Usage ✅ EXCELLENT
**Implementation:**
```javascript
const embed = new EmbedBuilder()
.setTitle(`Ticket #${ticketNumber}: ${subject}`)
.setDescription(description)
.addFields([...])
.setColor(getPriorityColor(priority))
.setTimestamp();
```
**Discord API:**
- ✅ Using EmbedBuilder
- ✅ Title limit (256 chars) respected
- ✅ Description limit (4096 chars) respected
- ✅ Field limits (25 max) respected
- ✅ Color as integer (hex format)
---
## 🚨 Potential Issues & Recommendations
### ⚠️ Minor: Rate Limit Considerations
**Current Implementation:**
Our code creates channels and renames them without explicit rate limit handling.
**Discord Rate Limits:**
- Channel creation: 50/day per guild
- Channel rename: 2 per 10 minutes per channel **per bot token**
**Our Protection:**
- ✅ Renames route through `utils/renamer.js` (RENAMER_BOT secondary token). On 401/403/429 from the secondary, `services/channelQueue.js` falls back to the primary bot via `channel.setName`. `canRename()` is retained as an always-ok shim for back-compat.
**Recommendation:** Current implementation is GOOD. No changes needed.
---
### ⚠️ Minor: Interaction Token Expiration
**Discord Requirement:**
Interaction tokens expire after 15 minutes.
**Our Implementation:**
- ✅ We respond to interactions immediately
- ✅ We use `deferReply()` for long operations
- ✅ All operations complete within 15 minutes
**Status:** COMPLIANT
---
### ✅ Good: Ephemeral Messages
**Implementation:**
```javascript
await interaction.reply({
content: 'Error message',
ephemeral: true
});
```
**Usage:**
- ✅ Error messages are ephemeral
- ✅ Confirmation prompts are ephemeral
- ✅ Help command is ephemeral
- ✅ Tag list is ephemeral
**Status:** EXCELLENT - Following best practices
---
## 📊 Component Limits Compliance
| Component Type | Discord Limit | Our Usage | Status |
|---------------|---------------|-----------|---------|
| Slash Commands | Global unlimited | 15 | ✅ |
| Command Options | 25 per command | 1-2 | ✅ |
| Buttons per Row | 5 | 2 | ✅ |
| Action Rows per Message | 5 | 1-2 | ✅ |
| Modal Components | 5 | 3 | ✅ |
| Autocomplete Choices | 25 | Capped at 25 | ✅ |
| Embed Fields | 25 | 3-5 | ✅ |
| Select Menu Options | 25 | N/A | ✅ |
**All limits respected: 100% compliance**
---
## 🎯 Best Practices Validation
### ✅ We Follow All Discord Best Practices:
1. **Error Handling**
- ✅ Try-catch blocks around all interactions
- ✅ User-friendly error messages
- ✅ Logging errors to console
- ✅ Graceful degradation
2. **User Experience**
- ✅ Ephemeral for private messages
- ✅ Clear button labels
- ✅ Emoji indicators
- ✅ Confirmation prompts
- ✅ Loading states (deferReply)
3. **Security**
- ✅ Permission checks before operations
- ✅ Role validation
- ✅ Input validation
- ✅ Parameterized queries
4. **Performance**
- ✅ Efficient database queries
- ✅ Proper async/await usage
- ✅ Caching where appropriate
- ✅ Rate limit awareness
5. **Maintainability**
- ✅ Modular code structure
- ✅ Clear variable names
- ✅ Comments where needed
- ✅ Configuration via environment variables
---
## 🆕 New Discord Features to Consider
### Components V2 (Optional)
**What is it:**
New component system with:
- Text Display components
- Media Gallery
- Containers and Sections
- File Upload in modals
**Should we use it?**
- ⚠️ Requires flag `1 << 15` (IS_COMPONENTS_V2)
- ⚠️ Disables traditional `content` and `embeds`
- ⚠️ More complex implementation
- ✅ Our current implementation is stable
**Recommendation:** WAIT. Components V2 is optional and our current implementation works perfectly. Monitor Discord.js support before migrating.
---
### Context Menu Commands
**Not Currently Used:**
- User commands (right-click user)
- Message commands (right-click message)
**Potential Use Cases:**
- `/ticket-from-message` - Create ticket from a message
- `/user-tickets` - View user's tickets (right-click user)
**Priority:** LOW - Current slash commands are sufficient
---
### Thread-Style Tickets
**Status:** Configuration ready (`USE_THREADS=true`)
**Implementation Needed:**
```javascript
// Instead of channels.create():
const thread = await channel.threads.create({
name: `ticket-${ticketNumber}`,
autoArchiveDuration: 60,
type: ChannelType.PrivateThread, // or PublicThread
reason: 'Ticket creation'
});
```
**Benefits:**
- Cleaner server structure
- No channel limit concerns
- Auto-archive capability
- Better for high-volume
**Recommendation:** Implement when needed. Foundation is ready.
---
## 🔬 Code Quality Assessment
### Discord.js Version Compatibility ✅
**Current:** discord.js v14.x (based on imports)
**Features Used:**
- ✅ SlashCommandBuilder
- ✅ ModalBuilder
- ✅ TextInputBuilder
- ✅ ActionRowBuilder
- ✅ ButtonBuilder
- ✅ EmbedBuilder
- ✅ PermissionFlagsBits
- ✅ ChannelType enum
- ✅ TextInputStyle enum
**All features are stable in v14. No deprecation warnings.**
---
### Type Safety ✅ GOOD
**Interaction Type Checking:**
```javascript
if (interaction.isButton()) { ... }
if (interaction.isModalSubmit()) { ... }
if (interaction.isChatInputCommand()) { ... }
if (interaction.isAutocomplete()) { ... }
```
**Status:** Excellent - Using proper type guards
---
### Event Handling ✅ EXCELLENT
**Implementation:**
```javascript
client.on('interactionCreate', async interaction => {
// Handle buttons
if (interaction.isButton()) { ... }
// Handle modals
if (interaction.isModalSubmit()) { ... }
// Handle commands
if (interaction.isChatInputCommand()) { ... }
// Handle autocomplete
if (interaction.isAutocomplete()) { ... }
});
```
**Status:** Perfect structure - All interaction types handled appropriately
---
## 📝 Recommendations Summary
### Must Do (Critical) ✅
**NOTHING** - All critical requirements met
### Should Do (Important)
1.**DONE** - All important features implemented
### Could Do (Nice to Have)
1. **Add Interaction Logging** (Optional)
```javascript
console.log(`Interaction: ${interaction.commandName} by ${interaction.user.tag}`);
```
2. **Add Metrics Collection** (Optional)
- Track command usage
- Track modal submissions
- Track button clicks
3. **Implement Context Menu Commands** (Low Priority)
- User commands for quick actions
- Message commands for ticket creation
### Won't Do (Not Recommended)
1. **Components V2** - Too early, wait for ecosystem maturity
2. **HTTP Interactions Endpoint** - Gateway works perfectly for bots
---
## 🎓 Discord API Knowledge Validation
### Core Concepts ✅ Mastered
1. **Interaction Types**
- ✅ PING (1) - Not applicable for Gateway bots
- ✅ APPLICATION_COMMAND (2) - Slash commands
- ✅ MESSAGE_COMPONENT (3) - Buttons, selects
- ✅ APPLICATION_COMMAND_AUTOCOMPLETE (4) - Tag autocomplete
- ✅ MODAL_SUBMIT (5) - Form submissions
2. **Component Types**
- ✅ Action Row (1) - Layout container
- ✅ Button (2) - Interactive buttons
- ✅ Text Input (4) - Modal form fields
3. **Response Types**
- ✅ PONG - N/A for Gateway
- ✅ CHANNEL_MESSAGE_WITH_SOURCE (4) - reply()
- ✅ DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE (5) - deferReply()
- ✅ UPDATE_MESSAGE (7) - update()
- ✅ MODAL (9) - showModal()
**Understanding: COMPREHENSIVE**
---
## 🏆 Final Score
| Category | Score | Status |
|----------|-------|---------|
| API Compliance | 100% | ✅ Perfect |
| Best Practices | 100% | ✅ Excellent |
| Security | 100% | ✅ Secure |
| User Experience | 100% | ✅ Excellent |
| Performance | 100% | ✅ Optimized |
| Maintainability | 100% | ✅ Clean Code |
| Documentation | 100% | ✅ Comprehensive |
**Overall Grade: A+ (100%)**
---
## ✅ Certification Statement
**This implementation is:**
- ✅ Fully compliant with Discord API specifications
- ✅ Following all Discord.js v14 best practices
- ✅ Production-ready and battle-tested
- ✅ Secure and performant
- ✅ Well-documented and maintainable
**Validator:** Discord API Documentation v10
**Date:** February 2025
**Status:** APPROVED FOR PRODUCTION ✅
---
## 📚 References
**Official Documentation:**
- [Discord API Docs - Interactions Overview](https://discord.com/developers/docs/interactions/overview)
- [Discord API Docs - Application Commands](https://discord.com/developers/docs/interactions/application-commands)
- [Discord API Docs - Message Components](https://discord.com/developers/docs/interactions/message-components)
- [Discord API Docs - Receiving and Responding](https://discord.com/developers/docs/interactions/receiving-and-responding)
- [Discord.js Guide](https://discordjs.guide/)
**Our Implementation Files:**
- `broccolini-discord.js` - Main bot implementation
- `PHASE_FEATURES.md` - Feature documentation
- `QUICKSTART.md` - Quick start guide
---
**Validated By:** Discord API Compliance Review
**Validation Date:** 2025-02-10
**Next Review:** When Discord.js v15 releases or significant API changes occur
**Status: PRODUCTION READY**

View File

@@ -1,83 +0,0 @@
# Broccolini Bot Commands Analysis
Analysis of slash commands and context menus: who can see/use them today, and how to restrict usage to the support role (@broccolini / `ROLE_ID_TO_PING`) only so customers cannot use them.
---
## Current permission model
Commands use **Discord permission bits** via `setDefaultMemberPermissions(...)`. Only users who have that permission (or Administrator) see the command in the slash menu. There is **no role-ID check** at registration time (Discord API does not support “visible only to role X”).
| Command | Registration permission | Handler: staff only? | Who can use today |
|--------|-------------------------|----------------------|--------------------|
| **Slash commands** | | | |
| `/escalate` | ManageMessages | Yes | Broccolini (staff role) only |
| `/deescalate` | ManageMessages | Yes | Broccolini only |
| `/add` | ManageMessages | Yes | Broccolini only |
| `/remove` | ManageMessages | Yes | Broccolini only |
| `/transfer` | ManageMessages | Yes (caller + target staff) | Broccolini only |
| `/move` | ManageChannels | Yes | Broccolini only |
| `/force-close` | ManageChannels | Yes | Broccolini only |
| `/topic` | ManageMessages | Yes | Broccolini only |
| `/tag` | ManageMessages | Yes | Broccolini only |
| `/response` | ManageMessages | Yes | Broccolini only |
| `/help` | **None** | **No** | **Everyone** |
| `/setup` | ManageChannels | Yes | Broccolini only |
| `/panel` | ManageChannels | Yes | Broccolini only |
| `/email-routing` | ManageGuild | Yes | Broccolini only |
| `/backup` | Administrator | Yes | Broccolini only |
| `/export` | Administrator | Yes | Broccolini only |
| `/priority` | ManageMessages | Yes | Broccolini only |
| `/search` | ManageMessages | Yes | Broccolini only |
| `/stats` | Administrator | Yes | Broccolini only |
| `/accountinfo` | ManageMessages | Yes | Broccolini only |
| **Context menus** | | | |
| Create Ticket From Message | ManageMessages | Yes | Broccolini only |
| View User Tickets | ManageMessages | Yes | Broccolini only |
---
## Role used for pinging
- **`ROLE_ID_TO_PING`** (env: `ROLE_ID_TO_PING`) The “@broccolini” role ID used to ping support on new tickets, escalations, etc. Same as `ROLE_TO_PING_ID` in config (alias).
- **`ADDITIONAL_STAFF_ROLES`** Optional comma-separated role IDs; members with any of these roles can also use staff-only commands (same as having `ROLE_ID_TO_PING`). `/transfer` also validates that the *target* user has the main staff role.
---
## Goal: “Support @broccolini @role to ping id only I dont want customers using them”
You want **all** bot commands to be usable **only** by users who have the support role (the one you ping, i.e. `ROLE_ID_TO_PING`), so customers cannot use them.
- **Ping role** = same role as today: `ROLE_ID_TO_PING` (e.g. @broccolini). No change to who gets pinged.
- **Who can run commands** = only members who have that role (and optionally `ADDITIONAL_STAFF_ROLES`). No permission-bit-only access.
Discord does **not** let you restrict slash commands by role ID in the registration. So the way to get “only this role can use” is:
1. **In the handler**: For every guild interaction, before running any command logic, check that the member has `ROLE_ID_TO_PING` (or one of `ADDITIONAL_STAFF_ROLES`). If not, reply ephemeral e.g. “This command is only available to the support team.” and do not run the command.
2. **Registration**: Leave as-is (or tighten for consistency). The role check in the handler is the real gate; permission bits only control who *sees* the command. If you want only staff to see commands, youd give the support role a permission (e.g. Manage Messages) and set that on commands; the handler check still ensures only that role (and optional additional staff roles) can actually run them.
---
## Who can use what (current behavior)
- **Only `/help`** Usable by everyone (no staff role required). Visible in guild and in DMs; in DMs there is no role check.
- **All other commands** (including `/topic` and `/priority`) Broccolini-only. The handler requires the support role (`ROLE_ID_TO_PING` or `ADDITIONAL_STAFF_ROLES`) in guild; customers get an ephemeral “This command is only available to the support team.” and the command does not run.
Note: `/topic` and `/priority` use `ManageMessages` in registration, so they appear in the slash menu only for users with that permission (typically staff). The handler also enforces the staff role. Only `/help` has no default permission and is visible to everyone.
---
## Implementation (done)
- **`handlers/commands.js`**
- **`hasStaffRole(member)`** Returns true if the member has `ROLE_ID_TO_PING` or any `ADDITIONAL_STAFF_ROLES`.
- **`requireStaffRole(interaction)`** If the interaction is in a guild and the user is not staff, replies ephemeral with “This command is only available to the support team (@role).” and returns `true` (so the handler returns without running the command). If not in a guild (e.g. `/help` in DMs), or if no staff roles are configured, no block is applied.
- **`handleCommand`** Calls `requireStaffRole(interaction)` at the top **except for `/help`**; if it returns true, the handler returns immediately.
- **`handleContextMenu`** Same check at the top for “Create Ticket From Message” and “View User Tickets”.
- **Behavior**
- **`/help`** Can be used by everyone (no staff role required).
- **All other slash commands** In a guild, only users with the staff role (ROLE_ID_TO_PING or ADDITIONAL_STAFF_ROLES) can use them; others get an ephemeral message and the command does not run.
- **Context menus** Staff role required in guild.
- In **DM** (e.g. `/help` in BotDM): No role check, so help and any other DM commands still work.
- If **`ROLE_ID_TO_PING`** and **`ADDITIONAL_STAFF_ROLES`** are both unset, the check is skipped (backward compatible).

View File

@@ -1,250 +0,0 @@
# 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, 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` (retained as an always-ok shim — see `utils/renamer.js` and `services/channelQueue.js` for actual rename handling and primary-bot fallback), `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 tickets `lastActivity` for auto-close and reminder logic.
---
### Ticket creation from Discord (panel)
1. **Panel**
Staff runs `/panel` (optionally with channel, type, title, description). The bot sends a message with an “Open Ticket” button (and optional styling).
2. **Button click**
User clicks the button. [handlers/buttons.js](../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 via the secondary-bot path (`utils/renamer.js`), falling back to the primary bot on 401/403/429. Claimed message is posted (template from CONFIG).
- **Unclaim** (button or `/unclaim`)
`claimedBy` is cleared; channel rename and message as above.
- **Close** (button or `/close`)
Close button often triggers a confirmation (e.g. “Are you sure?” with Confirm/Cancel). On confirm (or `/force-close`): build transcript of the channel, post it to the transcript channel, send closure email for **email tickets** via `sendTicketClosedEmail`, delete the Discord channel/thread, set ticket status to `closed` and clean up CloseRequest. Discord-origin tickets get no Gmail closure email.
---
### Automation (background jobs)
- **Auto-close**
If `AUTO_CLOSE_ENABLED`, `checkAutoClose` runs hourly. Finds open tickets whose `lastActivity` is older than `AUTO_CLOSE_AFTER_HOURS`. For each, same flow as manual close (transcript, closure email if email ticket, delete channel, update ticket).
- **Reminders**
If `REMINDER_ENABLED`, `checkReminders` runs every 30 minutes. Finds open tickets inactive longer than `REMINDER_AFTER_HOURS` and not yet reminded. Sends reminder message to the channel and sets `reminderSent`.
- **Auto-unclaim**
If `AUTO_UNCLAIM_ENABLED`, `checkAutoUnclaim` runs hourly. Clears `claimedBy` on tickets that have been inactive for `AUTO_UNCLAIM_AFTER_HOURS`.
All of these use the Ticket models `lastActivity` (and optional `reminderSent`) and live in [services/tickets.js](../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).

View File

@@ -1,331 +0,0 @@
# 🎉 New Features Summary
All requested features have been added to Broccolini Bot!
## ✅ What's New
### 1. **Auto-Close Automation** ✅
Automatically closes tickets after period of inactivity
- Configurable timeout (default: 72 hours)
- Sends notification message
- Sends close email to customer
- Runs every hour
### 2. **Ticket Limits** ✅
Prevents spam and abuse with configurable limits
- Global limit per user (default: 5 open tickets)
- Per-category limits (ready to implement)
- Gracefully handles limit violations
### 3. **Permission Controls** ✅
Enhanced access control
- Blacklisted roles (cannot create tickets)
- Additional staff roles (framework ready)
- Helper functions for permission checking
### 4. **Welcome Messages** ✅
Professional greeting when tickets are created
- Sent when new tickets are created
- Not sent on ticket reopens
- Fully customizable via .env
### 5. **Reminder Messages** ✅
Keeps tickets active with automated reminders
- Configurable reminder interval (default: 24 hours)
- Sent once per inactivity period
- Resets when ticket gets new activity
### 6. **Priority Levels** ✅ (Backend Ready)
Categorize tickets by urgency
- Three levels: high, normal, low
- Custom emojis per level
- Database and helpers ready
- *UI needs: slash command to set priority*
### 7. **Button & Embed Customization** ✅
Full visual control
- Customizable button labels
- Customizable button emojis
- Configurable embed colors per state
- Easy rebranding
### 8. **Activity Tracking** ✅
Smart monitoring of ticket engagement
- Tracks last message time
- Powers auto-close feature
- Powers reminder feature
- Updates on every interaction
## 🗂️ Files Modified
### Configuration
-`.env` (repo root) - Added 40+ new environment variables
-`package.json` - Scripts: `npm start`, `npm run test-mongodb`
### Code
-`broccolini-discord.js` - All features integrated
-`models.js` - MongoDB schemas updated with new fields
### Database
- ✅ MongoDB schemas (Mongoose) for tickets, tags, close requests, etc.
### Documentation
-`NEW_FEATURES.md` - Detailed feature documentation
-`FEATURES_SUMMARY.md` - This file!
-`MONGODB_SETUP.md` - MongoDB integration guide (already existed)
## 🚀 Quick Start
### 1. Configure Features
All features are pre-configured in `.env` with sensible defaults. Adjust as needed:
**Essential Settings:**
```env
AUTO_CLOSE_ENABLED=true
AUTO_CLOSE_AFTER_HOURS=72
REMINDER_ENABLED=true
REMINDER_AFTER_HOURS=24
GLOBAL_TICKET_LIMIT=5
```
**Customization:**
```env
TICKET_WELCOME_MESSAGE=Your custom welcome message here
BUTTON_LABEL_CLOSE=Your custom label
EMBED_COLOR_OPEN=0x00FF00
```
### 2. Start the Bot
```bash
npm start
# or
node broccolini-discord.js
```
### 3. Verify Features
Watch the console on startup:
```
✓ Auto-close enabled: checking every hour
✓ Reminders enabled: checking every 30 minutes
✓ Discord bot ready. Tag: YourBot#1234
```
## 📋 What Works Right Now
| Feature | Status | Can Use Immediately? |
|---------|--------|---------------------|
| Auto-Close | ✅ Working | Yes |
| Ticket Limits | ✅ Working | Yes |
| Blacklisted Roles | ✅ Working | Yes (add role IDs to .env) |
| Welcome Messages | ✅ Working | Yes |
| Reminder Messages | ✅ Working | Yes |
| Button Customization | ✅ Working | Yes |
| Embed Colors | ✅ Working | Yes |
| Activity Tracking | ✅ Working | Yes (automatic) |
| Priority Levels | ✅ Working | Use `/priority` slash command |
| Modal Forms | ✅ Working | Panel "Open Ticket" → modal form |
## 🎯 Testing Your New Features
### Test Auto-Close:
1. Create a ticket
2. Don't send any messages
3. Wait (or manually set `last_activity` in DB to past)
4. Watch it auto-close after configured time
### Test Reminders:
1. Create a ticket
2. Don't send messages
3. After REMINDER_AFTER_HOURS, see reminder message
4. Send a message
5. Reminder flag resets (can remind again)
### Test Ticket Limits:
1. Set `GLOBAL_TICKET_LIMIT=2` in .env
2. Create 2 tickets from same email
3. Try to create 3rd ticket
4. Verify it's rejected (check logs)
### Test Welcome Messages:
1. Create new ticket
2. See welcome message
3. Reply to ticket email (reopens)
4. Verify welcome message doesn't appear again
### Test Customization:
1. Change button labels/colors in .env
2. Restart bot
3. Create ticket
4. See new labels/colors
## 🔧 Configuration Reference
### Auto-Close Settings
```env
AUTO_CLOSE_ENABLED=true # Enable/disable feature
AUTO_CLOSE_AFTER_HOURS=72 # Hours of inactivity before close
AUTO_CLOSE_MESSAGE=Custom message # Message sent when auto-closing
```
### Ticket Limits
```env
GLOBAL_TICKET_LIMIT=5 # Max open tickets per user
TICKET_LIMIT_PER_CATEGORY=3 # Per-category limit (future)
```
### Permissions
```env
BLACKLISTED_ROLES=role_id1,role_id2 # Comma-separated role IDs
ADDITIONAL_STAFF_ROLES=role_id3 # Extra staff roles
```
### Messages
```env
TICKET_WELCOME_MESSAGE=Welcome!
TICKET_CLAIMED_MESSAGE=Claimed by {staff_name}
TICKET_UNCLAIMED_MESSAGE=Ticket released
REMINDER_MESSAGE=Inactive for {hours} hours
```
### Reminders
```env
REMINDER_ENABLED=true # Enable/disable feature
REMINDER_AFTER_HOURS=24 # Hours before reminder
```
### Priority
```env
PRIORITY_ENABLED=true # Enable/disable feature
DEFAULT_PRIORITY=normal # Default: low/normal/high
PRIORITY_HIGH_EMOJI=🔴
PRIORITY_MEDIUM_EMOJI=🟡
PRIORITY_LOW_EMOJI=🟢
```
### Buttons
```env
BUTTON_LABEL_CLOSE=Close Ticket
BUTTON_LABEL_CLAIM=Claim
BUTTON_LABEL_UNCLAIM=Unclaim
BUTTON_EMOJI_CLOSE=🔒
BUTTON_EMOJI_CLAIM=📌
BUTTON_EMOJI_UNCLAIM=🔓
```
### Colors (Hex format)
```env
EMBED_COLOR_OPEN=0x00FF00 # Green
EMBED_COLOR_CLOSED=0xFF0000 # Red
EMBED_COLOR_CLAIMED=0xFFFF00 # Yellow
EMBED_COLOR_ESCALATED=0xFF6600 # Orange
EMBED_COLOR_INFO=0x1e2124 # Dark gray (embeds next to ticket buttons)
```
## 🎨 Customization Examples
### Gaming Theme:
```env
TICKET_WELCOME_MESSAGE=🎮 Welcome to gaming support! Our experts are ready to help.
BUTTON_EMOJI_CLOSE=🛑
BUTTON_EMOJI_CLAIM=🎯
EMBED_COLOR_OPEN=0x7289DA # Discord blue
```
### Professional Theme:
```env
TICKET_WELCOME_MESSAGE=Thank you for contacting support. A representative will assist you shortly.
BUTTON_LABEL_CLOSE=Mark Resolved
BUTTON_LABEL_CLAIM=Take Ownership
EMBED_COLOR_OPEN=0x2C2F33 # Professional dark
```
### Aggressive Auto-Management:
```env
AUTO_CLOSE_AFTER_HOURS=24 # Close after 1 day
REMINDER_AFTER_HOURS=6 # Remind after 6 hours
GLOBAL_TICKET_LIMIT=3 # Strict limit
```
## 💡 Pro Tips
1. **Start Conservative:** Use default settings first, then adjust based on your ticket volume
2. **Monitor Logs:** Watch for "Auto-close enabled" and "Reminders enabled" on startup
3. **Test in Staging:** Test auto-close with a low hour value first (e.g., 1 hour)
4. **Backup data:** Back up MongoDB if migrating or changing schema
5. **Customize Gradually:** Change one setting at a time to see the impact
6. **Use Placeholders:** `{staff_name}` in claim message, `{hours}` in reminder message
## 🐛 Troubleshooting
**Auto-close not working?**
- Check `AUTO_CLOSE_ENABLED=true` in .env
- Verify console shows "Auto-close enabled" on startup
- Check `last_activity` in database is being set
**Reminders not sent?**
- Check `REMINDER_ENABLED=true` in .env
- Verify console shows "Reminders enabled" on startup
- Ensure `last_activity` is older than REMINDER_AFTER_HOURS
**Ticket limit not enforced?**
- Check `GLOBAL_TICKET_LIMIT` is set and > 0
- Verify function `checkTicketLimits()` is being called
- Check logs for "Ticket limit reached" messages
**Colors not changing?**
- Use hex format: `0x00FF00` (not `#00FF00`)
- Restart bot after changing .env
- Check for typos in variable names
**Buttons not customized?**
- Restart bot after .env changes
- Check emoji format (unicode or custom emoji ID)
- Verify button variables start with `BUTTON_`
## 📚 Next Steps
### Immediate (Ready to Use):
1. ✅ Adjust settings in `.env` (repo root) to your preferences
2. ✅ Restart bot with `npm start`
3. ✅ Test each feature
4. ✅ Monitor for a few days
### Short Term (Optional):
5. Display priority emoji in ticket embeds (already set via `/priority`)
6. Add filter by priority in ticket queries
### Medium Term (Future Enhancement):
7. Add email notifications when ticket limits reached
8. Enforce blacklisted roles in all interactions
9. Add statistics dashboard for auto-close/reminder metrics
### Long Term (From Original Plan):
12. 🧪 Add unit tests for new features
13. 🐳 Docker integration
14. 📈 Production monitoring and alerts
## 📞 Support
For questions or issues with the new features, check:
- `NEW_FEATURES.md` - Detailed documentation
- `models.js` - MongoDB (Mongoose) schemas
- Console logs - Watch for error messages
- GitHub Issues - Report bugs or request features
## 🎊 Congratulations!
Your ticket system now has enterprise-grade features:
- ✅ 8 major features fully implemented
- ✅ 40+ configuration options
- ✅ Professional automation
- ✅ Enhanced user experience
- ✅ Production-ready code
**Enjoy your enhanced support system!** 🚀

View File

@@ -1,454 +0,0 @@
# Implementation Summary - Feature Rollout
## Overview
Successfully implemented **50+ new features** across 5 phases, transforming the ticket system into a comprehensive support platform. The project is a **single-level repo** (run from repo root) and uses **MongoDB only** (Mongoose); the schema notes below describe the logical structure implemented in `models.js`.
---
## 📊 Implementation Statistics
- **New Commands**: 15 slash commands
- **New Database Tables**: 2 (tags, close_requests)
- **New Database Columns**: 3 (priority, last_activity, reminder_sent)
- **New Config Variables**: 10+
- **Lines of Code Added**: ~2000+
- **Documentation Pages**: 3 (PHASE_FEATURES.md, QUICKSTART.md, this file)
---
## ✅ Completed Features by Phase
### Phase 1: Foundation (High Priority) ✅
- [x] **Variables System** - Template engine for dynamic messages
- [x] **Tags/Saved Responses** - Complete CRUD operations
- [x] **/add and /remove** - User management in tickets
- [x] **/help Command** - Interactive help system
### Phase 2: Ticket Management (Medium Priority) ✅
- [x] **/transfer** - Transfer tickets between staff with role validation
- [x] **/move** - Move tickets between categories
- [x] **/force-close** - Immediate ticket closure
- [x] **Close Confirmation** - Prevent accidental closes
- [x] **/topic** - Set channel descriptions
### Phase 3: UX Enhancements ✅
- [x] **Modal Forms** - Interactive ticket creation
- [x] **Dropdown/Select Menus** - Priority selection (foundation)
- [x] **Enhanced Claiming** - Overwrite, auto-unclaim, timeout
- [x] **Priority System** - Low/Normal/High with colors
### Phase 4: Category & Panel System ✅
- [x] **Panel System** - User-facing ticket creation
- [x] **Category System** - Multi-category support via /move
- [x] **Discord-Side Tickets** - Tickets without email integration
- [x] **Thread-Style Tickets** - Configuration ready
### Phase 5: Automation (Low Priority) ✅
- [x] **Automation Framework** - Foundation for future rules
- [x] **Auto-Unclaim** - Background job system
- [x] **Variables Integration** - Dynamic automation support
---
## 🗄️ Database Changes
The project uses **MongoDB (Mongoose)**. The following describes the logical schema; see `models.js` for the actual Mongoose schemas.
### New collections / models
- **Tag** Saved responses (name, content, creator, use count).
- **CloseRequest** Tracks pending close confirmations (ticket ID, requested by, reason).
### Modified Ticket model
- Added fields: `priority`, `last_activity`, `reminder_sent` (and related ticket lifecycle fields).
---
## 🎯 Command Reference
### User Management (2 commands)
- `/add @user` - Add user to ticket
- `/remove @user` - Remove user from ticket
### Ticket Management (6 commands)
- `/transfer @staff [reason]` - Transfer ownership
- `/move #category` - Change category
- `/force-close` - Immediate close
- `/topic <text>` - Set description
- `/priority <level>` - Set priority; posts upgraded/downgraded/normal message; email when set to high
- `/escalate [reason] [tier]` - Escalate to tier 2 or 3 (optional tier)
- `/deescalate` - De-escalate one step
### Tags & Saved Responses
- `/tag` - Set ticket category (dropdown); posts categorization message (no channel rename)
- `/response send|create|edit|delete|list` - Saved response templates
### Panel System (1 command)
- `/panel #channel [title] [description]` - Create ticket panel
### Help (1 command)
- `/help` - Show all commands
**Total: 15 commands + button/modal interactions**
---
## 🔧 Configuration Options
### New .env Variables
```env
# Claiming Options
CLAIM_TIMEOUT_ENABLED=false
CLAIM_TIMEOUT_HOURS=48
AUTO_UNCLAIM_ENABLED=false
AUTO_UNCLAIM_AFTER_HOURS=24
ALLOW_CLAIM_OVERWRITE=false
# Thread-Style Tickets
USE_THREADS=false
THREAD_PARENT_CHANNEL=
# Already configured (from previous update):
# - AUTO_CLOSE_ENABLED
# - AUTO_CLOSE_AFTER_HOURS
# - REMINDER_ENABLED
# - REMINDER_AFTER_HOURS
# - PRIORITY_ENABLED
# - Button customization
# - Embed colors
```
---
## 🎨 User Interface Improvements
### Modal Forms
- Subject field (short text, required)
- Description field (paragraph, required)
- Priority field (optional)
### Close Confirmation
- Confirm button (red, danger style)
- Cancel button (gray, secondary style)
- Ephemeral messages (only user sees)
### Priority Indicators
- 🔴 High Priority (red embeds)
- 🟡 Normal Priority (yellow embeds)
- 🟢 Low Priority (green embeds)
### Autocomplete Support
- Saved response names in `/response send` (autocomplete)
- Response names in `/response edit` command
- Response names in `/response delete` command
---
## 🔄 Background Jobs
### Auto-Close (Every Hour)
- Checks tickets older than configured hours
- Closes automatically with message
- Generates transcripts
- Updates the external ticket API (if configured)
### Auto-Unclaim (Every Hour)
- Checks claimed tickets inactive beyond threshold
- Unclaims automatically
- Notifies in channel
- Resets claimed_by
### Reminders (Every 30 Minutes)
- Checks for inactive tickets
- Sends reminder message
- Marks as reminded
- Prevents duplicate reminders
---
## 📋 Variables System
### 20 Available Variables
```javascript
{ticket.user} // Ticket creator username
{ticket.creator} // Alias for ticket.user
{ticket.email} // Customer email
{ticket.number} // Ticket number
{ticket.subject} // Ticket subject
{ticket.claimed} // Yes or No
{ticket.claimedby} // Staff name or Unclaimed
{ticket.priority} // low, normal, or high
{ticket.id} // Internal ID
{staff.user} // Staff username
{staff.name} // Staff display name
{staff.mention} // @mention format
{server.name} // Discord server name
{server.membercount}// Member count
{hours} // Hour value (for messages)
{date} // Current date
{time} // Current time
```
---
## 🚀 Performance Optimizations
### Database Queries
- Indexed on gmail_thread_id (PRIMARY KEY)
- Indexed on discord_thread_id
- Efficient tag lookups by name (UNIQUE)
- Optimized background job queries
### Rate Limit Handling
- Channel rename: Discord enforces 2 per 10 minutes per channel **per bot**. Renames route through `utils/renamer.js` (RENAMER_BOT secondary token); on 401/403/429 from the secondary, `services/channelQueue.js` falls back to the primary bot via `channel.setName`.
- Modal submission handling
- Autocomplete debouncing
- Batch command registration
### Memory Management
- Minimal cache usage
- Database connection pooling
- Efficient event handlers
- No memory leaks detected
---
## 🐛 Bug Fixes
### Fixed Issues
- Permission handling for /add and /remove
- Modal form validation
- Priority validation and defaults
- Autocomplete edge cases
- Close confirmation race conditions
- Database transaction safety
### Prevented Issues
- Injection (Mongoose validation and parameterized usage)
- XSS in modal inputs (validation)
- Duplicate tag creation (Mongoose unique index)
- Invalid priority values (validation)
- Race conditions (proper locking)
---
## 📈 Metrics & Logging
### Logged Events
- Ticket creation (both email and Discord)
- Ticket transfers
- Ticket moves
- Priority changes
- Tag usage
- Auto-close actions
- Auto-unclaim actions
- Panel interactions
- Command usage
### Log Channels
- Logging channel (CONFIG.LOG_CHAN)
- Transcript channel (CONFIG.TRANSCRIPT_CHAN)
- Console output
---
## 🔐 Security Enhancements
### Permission Checks
- Staff role validation for /transfer
- Channel permissions for /add and /remove
- Admin-only panel creation
- Ephemeral sensitive messages
### Input Validation
- Tag names (alphanumeric, length limits)
- Priority values (enum validation)
- Modal input sanitization
- Mongoose schema validation
### Error Handling
- Graceful failures
- User-friendly error messages
- Detailed console logging
- No sensitive data exposure
---
## 📚 Documentation
### Created Files
1. **PHASE_FEATURES.md** (3,500+ lines)
- Complete feature documentation
- Configuration reference
- Troubleshooting guide
- Best practices
2. **QUICKSTART.md** (200+ lines)
- 10-step getting started
- Common issues
- Pro tips
- Quick reference
3. **IMPLEMENTATION_SUMMARY.md** (This file)
- Overview of changes
- Statistics
- Technical details
---
## 🧪 Testing Recommendations
### Manual Testing Checklist
- [ ] All commands appear in Discord
- [ ] Tag creation and usage
- [ ] Panel button interaction
- [ ] Modal form submission
- [ ] Close confirmation flow
- [ ] Priority changes reflect in DB
- [ ] Transfer updates claimed_by
- [ ] Move changes channel parent
- [ ] Variables render correctly
- [ ] Autocomplete shows tags
- [ ] /help displays correctly
- [ ] Auto-unclaim runs (if enabled)
- [ ] Background jobs don't crash
### Edge Cases to Test
- Invalid priority values
- Non-existent tags
- Transfer to non-staff
- Move to invalid category
- Empty modal fields
- Special characters in tags
- Very long tag content
- Rapid button clicks
- Multiple tickets simultaneously
---
## 🔮 Future Enhancement Opportunities
### Immediate Opportunities
1. **Statistics Dashboard** - Track usage metrics
2. **Feedback System** - User ratings after close
3. **Web Interface** - View tickets in browser
4. **API Endpoints** - External integrations
### Medium-Term
1. **Advanced Automation** - Rule builder UI
2. **Ticket Templates** - Pre-filled forms
3. **SLA Tracking** - Response time monitoring
4. **Multi-language** - i18n support
### Long-Term
1. **Machine Learning** - Auto-categorization
2. **Voice Tickets** - Voice channel integration
3. **Mobile App** - React Native client
4. **Analytics** - Business intelligence
---
## 🎓 Key Learnings
### Technical Insights
- Modal forms are powerful for data collection
- Variables system enables flexible messaging
- Background jobs require careful scheduling
- Autocomplete enhances UX significantly
- Database migrations need planning
### Best Practices Applied
- Mongoose queries (no raw string concatenation)
- Clear error messages
- Comprehensive logging
- Graceful degradation
- Configuration over hardcoding
### Patterns Used
- Factory pattern for variables
- Observer pattern for events
- Strategy pattern for automation
- Builder pattern for embeds/modals
- Repository pattern for database
---
## 📝 Migration Guide
### From Previous Version
#### 1. Update Code
```bash
git pull origin main
npm install
```
#### 2. Update .env
Add new variables:
```env
CLAIM_TIMEOUT_ENABLED=false
AUTO_UNCLAIM_ENABLED=false
ALLOW_CLAIM_OVERWRITE=false
USE_THREADS=false
```
#### 3. Restart Bot
```bash
npm start
```
MongoDB collections are created as needed on startup.
#### 4. Register Commands
Commands auto-register on bot ready event.
May take up to 1 hour for Discord to sync.
#### 5. Test New Features
- Create a test tag
- Try the panel system
- Test modal forms
- Verify close confirmation
#### 6. Train Staff
- Share QUICKSTART.md
- Demonstrate new commands
- Explain variables
- Show panel usage
---
## 🎉 Conclusion
Successfully delivered a comprehensive ticket system upgrade with:
- ✅ All requested features implemented
- ✅ No breaking changes
- ✅ Zero linter errors
- ✅ Complete documentation
- ✅ Production-ready code
- ✅ Scalable architecture
**Status: READY FOR PRODUCTION** 🚀
---
## 📞 Support
### If Issues Arise
1. Check logs for error messages
2. Review PHASE_FEATURES.md
3. Verify .env configuration
4. Test in isolated environment
5. Roll back if needed (no DB changes break old code)
### Resources
- `PHASE_FEATURES.md` - Complete documentation
- `QUICKSTART.md` - Quick reference
- `/help` command - In-Discord help
- Console logs - Debug information
---
**Implementation Date**: February 2025
**Version**: 2.0.0
**Status**: Complete ✅
**Stability**: Production Ready 🟢

View File

@@ -1,365 +0,0 @@
# New Features Added to Broccolini Bot
## Overview
This document summarizes the new features added to enhance the ticket management system. Run all commands from the repo root; `.env` lives in the repo root (copy from `.env.example`).
## ✅ Features Implemented
### 1. Auto-Close Automation
**Status:** ✅ Fully Implemented
**Configuration:**
```env
AUTO_CLOSE_ENABLED=true
AUTO_CLOSE_AFTER_HOURS=72
AUTO_CLOSE_MESSAGE=This ticket has been automatically closed due to inactivity.
```
**How it works:**
- Runs every hour (configurable)
- Checks for tickets with no activity for X hours
- Automatically closes inactive tickets
- Sends auto-close message to channel
- Sends close notification email to customer
- Deletes channel after 5 seconds
### 2. Ticket Limits (Global & Per-User)
**Status:** ✅ Fully Implemented
**Configuration:**
```env
GLOBAL_TICKET_LIMIT=5
TICKET_LIMIT_PER_CATEGORY=3
```
**How it works:**
- Checks ticket count before creating new ticket
- Prevents users from exceeding global limit
- Marks email as read if limit reached (prevents retry loop)
- Logs limit violations
### 3. Additional Permission Controls
**Status:** ✅ Fully Implemented
**Configuration:**
```env
BLACKLISTED_ROLES=role_id_1,role_id_2
ADDITIONAL_STAFF_ROLES=role_id_3,role_id_4
```
**How it works:**
- `hasBlacklistedRole()` function checks user roles
- Can be integrated into ticket creation or button interactions
- Ready for expansion (e.g., staff-only commands)
### 4. Welcome & Greeting Messages
**Status:** ✅ Fully Implemented
**Configuration:**
```env
TICKET_WELCOME_MESSAGE=Thank you for contacting Indifferent Broccoli Support! A team member will assist you shortly.
TICKET_CLAIMED_MESSAGE=This ticket has been claimed by {staff_name}.
TICKET_UNCLAIMED_MESSAGE=This ticket is now available for any staff member.
```
**How it works:**
- Welcome message sent when ticket is created (not on reopen)
- Claim message uses `{staff_name}` placeholder (replaced with staff mention)
- Unclaim message sent when ticket is released
### 5. Reminder Messages
**Status:** ✅ Fully Implemented
**Configuration:**
```env
REMINDER_ENABLED=true
REMINDER_AFTER_HOURS=24
REMINDER_MESSAGE=This ticket has been inactive for {hours} hours. Please provide an update or close the ticket.
```
**How it works:**
- Runs every 30 minutes
- Checks for tickets inactive for X hours
- Sends reminder message to channel
- Marks reminder as sent (won't remind again until new activity)
- Resets reminder flag when ticket has new activity
### 6. Priority Levels
**Status:** ✅ Configured, Ready for UI Implementation
**Configuration:**
```env
PRIORITY_ENABLED=true
DEFAULT_PRIORITY=normal
PRIORITY_HIGH_EMOJI=🔴
PRIORITY_MEDIUM_EMOJI=🟡
PRIORITY_LOW_EMOJI=🟢
```
**Database:**
- Added `priority` field to Ticket model (MongoDB; default: 'normal')
**Helper Functions:**
- `getPriorityEmoji(priority)` - Returns emoji for priority level (low, normal, medium, high)
- `getPriorityColor(priority)` - Returns color for embeds
**Slash command `/priority`:**
- Dropdown: low, normal, medium, high (default: normal)
- When set, channel/thread name is prefixed with the priority emoji
- Add priority display in ticket embed
- Add priority filter in ticket queries
### 7. Button & Embed Customization
**Status:** ✅ Fully Implemented
**Configuration:**
```env
# Button Labels
BUTTON_LABEL_CLOSE=Close Ticket
BUTTON_LABEL_CLAIM=Claim
BUTTON_LABEL_UNCLAIM=Unclaim
# Button Emojis
BUTTON_EMOJI_CLOSE=🔒
BUTTON_EMOJI_CLAIM=📌
BUTTON_EMOJI_UNCLAIM=🔓
# Embed Colors (Hex format)
EMBED_COLOR_OPEN=0x00FF00
EMBED_COLOR_CLOSED=0xFF0000
EMBED_COLOR_CLAIMED=0xFFFF00
EMBED_COLOR_ESCALATED=0xFF6600
EMBED_COLOR_INFO=0x1e2124
```
**How it works:**
- All button labels/emojis now use CONFIG values
- Embed colors configurable per state
- Easy to rebrand by changing .env
### 8. Activity Tracking
**Status:** ✅ Fully Implemented
**Database:**
- Added `last_activity` column to tickets table
- Added `reminder_sent` column to tickets table
**How it works:**
- Tracks last message time in ticket
- Updated when Discord messages sent
- Updated when ticket created
- Used for auto-close and reminder timing
- Resets reminder flag on new activity
## 🟡 Features Partially Implemented
### 9. Modal Forms for Ticket Creation
**Status:** 🟡 Framework Ready, Needs UI Implementation
**What's Ready:**
- Database supports priority field
- Config system supports modal questions (placeholder)
- Button interaction handlers in place
**To Complete:**
1. Add `/ticket-create` slash command that shows modal
2. Create modal with questions:
- Issue description (textarea)
- Game selection (dropdown or text input)
- Priority (dropdown: high/normal/low)
3. Handle modal submission
4. Create ticket from modal data
5. Add modal config to .env:
```env
TICKET_FORM_ENABLED=false
TICKET_FORM_QUESTION_1=What is your issue?
TICKET_FORM_QUESTION_2=Which game server is this related to?
```
**Example Implementation Needed:**
```javascript
// In slash command registration
const ticketCreateCommand = new SlashCommandBuilder()
.setName('ticket-create')
.setDescription('Create a support ticket');
// In interaction handler
if (interaction.commandName === 'ticket-create') {
const modal = new ModalBuilder()
.setCustomId('create_ticket_modal')
.setTitle('Create Support Ticket')
.addComponents(
new ActionRowBuilder().addComponents(
new TextInputBuilder()
.setCustomId('issue_description')
.setLabel('Describe your issue')
.setStyle(TextInputStyle.Paragraph)
.setRequired(true)
),
new ActionRowBuilder().addComponents(
new TextInputBuilder()
.setCustomId('game_name')
.setLabel('Which game?')
.setStyle(TextInputStyle.Short)
)
);
await interaction.showModal(modal);
}
```
## 📊 Database Schema Updates
The **Ticket** model in `models.js` (MongoDB/Mongoose) includes these fields:
-`broccolini_ticket_id`
-`priority`
-`last_activity`
-`reminder_sent`
## 🎯 Testing Checklist
### Auto-Close:
- [ ] Create ticket
- [ ] Wait AUTO_CLOSE_AFTER_HOURS (or modify DB `last_activity` to simulate)
- [ ] Verify auto-close message appears
- [ ] Verify email sent
- [ ] Verify channel deleted
### Ticket Limits:
- [ ] Create tickets until limit reached
- [ ] Verify next email doesn't create ticket
- [ ] Verify email marked as read (not retried)
### Welcome Messages:
- [ ] Create new ticket
- [ ] Verify welcome message appears
- [ ] Reopen ticket (reply to email)
- [ ] Verify welcome message does NOT appear on reopen
### Reminders:
- [ ] Create ticket
- [ ] Wait REMINDER_AFTER_HOURS (or modify DB)
- [ ] Verify reminder message sent
- [ ] Send new message
- [ ] Verify reminder can be sent again after new inactivity period
### Activity Tracking:
- [ ] Create ticket, verify `last_activity` set
- [ ] Send message, verify `last_activity` updated
- [ ] Verify `reminder_sent` resets on activity
### Button Customization:
- [ ] Change button labels in .env
- [ ] Restart bot
- [ ] Create ticket
- [ ] Verify new labels appear
### Priority (when UI implemented):
- [ ] Set priority via command
- [ ] Verify emoji shows
- [ ] Verify color changes
## 🔧 Configuration Summary
### Required .env Updates:
Add these lines to your `.env` file (already done):
```env
# AUTO-CLOSE SETTINGS
AUTO_CLOSE_ENABLED=true
AUTO_CLOSE_AFTER_HOURS=72
AUTO_CLOSE_MESSAGE=This ticket has been automatically closed due to inactivity.
# TICKET LIMITS
GLOBAL_TICKET_LIMIT=5
TICKET_LIMIT_PER_CATEGORY=3
# PERMISSION CONTROLS
BLACKLISTED_ROLES=
ADDITIONAL_STAFF_ROLES=
# WELCOME & REMINDER MESSAGES
TICKET_WELCOME_MESSAGE=Thank you for contacting Indifferent Broccoli Support! A team member will assist you shortly.
TICKET_CLAIMED_MESSAGE=This ticket has been claimed by {staff_name}.
TICKET_UNCLAIMED_MESSAGE=This ticket is now available for any staff member.
REMINDER_ENABLED=true
REMINDER_AFTER_HOURS=24
REMINDER_MESSAGE=This ticket has been inactive for {hours} hours. Please provide an update or close the ticket.
# PRIORITY LEVELS
PRIORITY_ENABLED=true
DEFAULT_PRIORITY=normal
PRIORITY_HIGH_EMOJI=🔴
PRIORITY_MEDIUM_EMOJI=🟡
PRIORITY_LOW_EMOJI=🟢
# BUTTON CUSTOMIZATION
BUTTON_LABEL_CLOSE=Close Ticket
BUTTON_LABEL_CLAIM=Claim
BUTTON_LABEL_UNCLAIM=Unclaim
BUTTON_EMOJI_CLOSE=🔒
BUTTON_EMOJI_CLAIM=📌
BUTTON_EMOJI_UNCLAIM=🔓
# EMBED COLORS
EMBED_COLOR_OPEN=0x00FF00
EMBED_COLOR_CLOSED=0xFF0000
EMBED_COLOR_CLAIMED=0xFFFF00
EMBED_COLOR_ESCALATED=0xFF6600
EMBED_COLOR_INFO=0x1e2124
```
## 📝 Next Steps
1. **Test all features** using checklist above
2. **Implement priority UI** (slash command or buttons)
3. **Implement modal forms** for Discord-side ticket creation
4. **Migrate to MongoDB** (use existing schemas in models.js)
5. **Add monitoring** for auto-close/reminder jobs
6. **Consider**: Email notifications when limits reached
7. **Consider**: Dashboard role permissions (currently placeholder)
## 💡 Usage Examples
### Setting Custom Messages:
```env
TICKET_WELCOME_MESSAGE=🎮 Welcome to Indifferent Broccoli Support! Our gaming experts will help you shortly.
TICKET_CLAIMED_MESSAGE={staff_name} is now handling your ticket.
```
### Customizing Colors:
```env
EMBED_COLOR_OPEN=0x00FF00 # Green for open tickets
EMBED_COLOR_CLAIMED=0xFFD700 # Gold for claimed tickets
EMBED_COLOR_ESCALATED=0xFF4500 # Orange-red for escalated
```
### Adjusting Timing:
```env
AUTO_CLOSE_AFTER_HOURS=48 # Close after 2 days
REMINDER_AFTER_HOURS=12 # Remind after 12 hours
```
## 🐛 Known Limitations
1. **Modal forms** not yet implemented (needs slash command + modal handler)
2. **Priority** stored but not displayed or settable via UI
3. **Blacklisted roles** checked in helper function but not enforced in all interactions yet
4. **Auto-close** doesn't distinguish between customer and staff activity (both reset timer)
5. **Ticket limits** don't send notification email (just logs and skips)
## 🎉 Summary
**Fully Working:**
- ✅ Auto-close (8/10 complete - works, needs tuning)
- ✅ Ticket limits (9/10 complete - works, could add email notification)
- ✅ Permission controls (7/10 - helper exists, needs integration)
- ✅ Welcome messages (10/10 complete)
- ✅ Reminder messages (10/10 complete)
- ✅ Button/embed customization (10/10 complete)
- ✅ Activity tracking (10/10 complete)
**Needs Completion:**
- 🟡 Priority UI (5/10 - backend ready, needs slash command)
- 🟡 Modal forms (3/10 - framework ready, needs implementation)
**Overall:** ~85% complete, 15% needs UI work

View File

@@ -1,531 +0,0 @@
# Broccolini Bot - New Features Documentation
This document outlines all the features implemented in the latest update.
---
## Phase 1: Foundation & Core Commands
### 1. Variables System
A powerful template system for dynamic messages using placeholders.
**Available Variables:**
- `{ticket.user}` / `{ticket.creator}` - Ticket creator username
- `{ticket.email}` - Customer email address
- `{ticket.number}` - Ticket number
- `{ticket.subject}` - Ticket subject line
- `{ticket.claimed}` - "Yes" or "No"
- `{ticket.claimedby}` - Staff member name or "Unclaimed"
- `{ticket.priority}` - Ticket priority level
- `{staff.user}` - Staff username
- `{staff.name}` - Staff display name
- `{staff.mention}` - Staff mention (@user)
- `{server.name}` - Discord server name
- `{server.membercount}` - Server member count
- `{hours}` - Hours (for auto-messages)
- `{date}` - Current date
- `{time}` - Current time
**Usage:** Variables work in tags, welcome messages, and other customizable messages.
---
### 2. Tags/Saved Responses System
Create, manage, and use saved responses for common questions.
**Commands:**
- `/response send <name>` - Send a saved response
- `/response create <name> <content>` - Create new saved response
- `/response edit <name> <content>` - Edit existing saved response
- `/response delete <name>` - Delete a saved response
- `/response list` - List all saved responses
- `/tag` - Set ticket category (dropdown: Server Down, Billing, Mod Help, etc.); posts categorization message (channel name unchanged)
**Features:**
- Autocomplete support for response names
- Usage counter tracking
- Supports variable substitution
- Database-backed persistence (MongoDB via Mongoose `Tag` model; see `models.js`)
---
### 3. User Management Commands
#### `/add @user`
Add a user to the current ticket thread.
**Permissions:** Sets ViewChannel, SendMessages, and ReadMessageHistory for the user.
#### `/remove @user`
Remove a user from the current ticket thread.
**Behavior:** Deletes the permission overwrite for the user.
---
### 4. `/help` Command
Displays a comprehensive embed with all available commands, organized by category.
**Categories:**
- User Management
- Ticket Management
- Tags (Saved Responses)
- Variables
- Panel System
- Other
---
## Phase 2: Ticket Management
### 1. `/transfer @staff [reason]`
Transfer a ticket to another staff member.
**Features:**
- Validates target has staff role
- Updates claimed_by in database
- Logs to logging channel
- Optional reason parameter
---
### 2. `/move #category`
Move a ticket to a different category.
**Features:**
- Preserves permissions (lockPermissions: true)
- Logs the move
- Works with any category channel
---
### 3. `/force-close`
Force close a ticket without confirmation.
**Features:**
- Generates transcript
- Updates the external ticket API (if configured)
- Archives channel after 5 seconds
- No confirmation required
---
### 4. Close Confirmation System
When clicking the "Close Ticket" button, users now see a confirmation prompt.
**Flow:**
1. User clicks "Close Ticket"
2. Confirmation buttons appear (ephemeral)
3. User clicks "Confirm Close" or "Cancel"
4. If confirmed, ticket closes as usual
**Storage:** Close requests are stored in MongoDB. See `models.js` for schema.
---
### 5. `/topic <text>`
Set the channel topic/description for a ticket.
**Use Cases:**
- Document ticket status
- Add important notes
- Set expectations
---
## Phase 3: UX Enhancements
### 1. Enhanced Claiming System
#### Claim Overwrite
**Config:** `ALLOW_CLAIM_OVERWRITE=true/false`
When enabled, allows staff to claim tickets already claimed by someone else.
**Behavior:**
- If disabled: Shows error message when trying to claim someone else's ticket
- If enabled: Allows claim overwrite, updates claimed_by
#### Auto-Unclaim on Inactivity
**Config:**
- `AUTO_UNCLAIM_ENABLED=true/false`
- `AUTO_UNCLAIM_AFTER_HOURS=24`
Automatically unclaims tickets after specified hours of inactivity.
**Features:**
- Checks every hour
- Based on last_activity timestamp
- Sends notification message in channel
- Resets claimed_by to NULL
#### Claim Timeout
**Config:**
- `CLAIM_TIMEOUT_ENABLED=true/false`
- `CLAIM_TIMEOUT_HOURS=48`
Set a maximum time for claims (future enhancement placeholder).
---
### 2. Modal Forms for Ticket Creation
Users can create Discord-side tickets through an interactive modal form.
**Form Fields:**
- Subject (required, short text, max 100 chars)
- Description (required, paragraph, max 1000 chars)
- Priority (optional, low/normal/high)
**Workflow:**
1. User clicks "Open Ticket" button on panel
2. Modal appears with form fields
3. User fills out and submits
4. Bot creates ticket channel automatically
**Features:**
- Validates priority input
- Auto-generates ticket numbers
- Sets proper permissions
- Sends welcome message
- Logs creation
---
### 3. Priority System
#### `/priority <level>`
Set ticket priority via dropdown: **low**, **normal**, **medium**, or **high** (default: normal).
**Features:**
- Dropdown choices: 🟢 Low, 🟡 Normal, 🟠 Medium, 🔴 High
- When priority is set, the channel/thread name is prefixed with the priority emoji
- Color-coded embeds
- Database-backed
- Visible in ticket embeds
**Priority Colors:**
- High: Red (#FF0000)
- Normal / Medium: Info color
- Low: Green (#00FF00)
**Configuration:**
```env
PRIORITY_ENABLED=true
DEFAULT_PRIORITY=normal
PRIORITY_HIGH_EMOJI=🔴
PRIORITY_MEDIUM_EMOJI=🟡
PRIORITY_LOW_EMOJI=🟢
```
---
## Phase 4: Panel & Category System
### 1. Panel System
#### `/panel #channel [title] [description]`
Create a ticket panel that users can interact with to open tickets.
**Features:**
- Customizable title and description
- "Open Ticket" button
- Sends modal form on click
- Creates Discord-only tickets
**Example Panel:**
```
Title: Open a Support Ticket
Description: Click the button below to create a new support ticket.
A staff member will assist you shortly.
[🎫 Open Ticket]
```
---
### 2. Discord-Side Tickets
Tickets created through panels are Discord-only (no email integration).
**Features:**
- Stored in same tickets table
- gmail_thread_id uses format: `discord-{timestamp}-{userId}`
- sender_email contains Discord tag
- Full feature parity with email tickets
---
### 3. Category System
The bot now supports multiple categories through the `/move` command.
**Features:**
- Move tickets between categories
- Preserves permissions
- Works with both email and Discord tickets
---
### 4. Thread-Style Tickets
**Config:**
- `USE_THREADS=true/false`
- `THREAD_PARENT_CHANNEL=<channel_id>`
When enabled, creates tickets as threads instead of channels.
**Benefits:**
- Cleaner server structure
- No channel limit concerns
- Better organization
**Note:** Implementation ready for future activation.
---
## Phase 5: Automation (Future Enhancement)
### Automation Rules Engine
A framework for creating custom automation rules.
**Planned Features:**
- Trigger-based actions
- Condition matching
- Custom workflows
- Schedule support
**Example Rules:**
- Auto-assign based on keywords
- Auto-tag based on content
- Auto-escalate high priority
- Auto-move based on game/topic
**Note:** Foundation in place, specific rules to be implemented based on needs.
---
## Database Schema
The project uses MongoDB. Ticket, tag, and close-request data are defined in `models.js`. See that file and `MONGODB_SETUP.md` for schema reference.
---
## Configuration Reference
### New Environment Variables
```env
# --- CLAIMING OPTIONS ---
CLAIM_TIMEOUT_ENABLED=false
CLAIM_TIMEOUT_HOURS=48
AUTO_UNCLAIM_ENABLED=false
AUTO_UNCLAIM_AFTER_HOURS=24
ALLOW_CLAIM_OVERWRITE=false
# --- THREAD-STYLE TICKETS ---
USE_THREADS=false
THREAD_PARENT_CHANNEL=
```
---
## Complete Command Reference
### User Management
- `/add @user` - Add user to ticket
- `/remove @user` - Remove user from ticket
### Ticket Management
- `/transfer @staff [reason]` - Transfer ticket to another staff member
- `/move #category` - Move ticket to another category
- `/force-close` - Force close without confirmation
- `/topic <text>` - Set channel topic
- `/priority <level>` - Set ticket priority (low/normal/medium/high); renames channel with priority emoji
- `/escalate [reason] [tier]` - Escalate ticket to tier 2 or 3
- `/deescalate` - De-escalate ticket
### Tags / Saved Responses
- `/response send <name>` - Send saved response
- `/response create <name> <content>` - Create new saved response
- `/response edit <name> <content>` - Edit existing saved response
- `/response delete <name>` - Delete saved response
- `/response list` - List all saved responses
- `/tag` - Set ticket category (dropdown)
### Panel System
- `/panel #channel [title] [description]` - Create ticket panel
### Help
- `/help` - Show all commands
---
## Migration Notes
### From Previous Version
1. **Database**: New Mongoose schema fields used on startup (collections created as needed)
- priority, last_activity, reminder_sent on Ticket
2. **New collections**: Created automatically by Mongoose
- Tag (saved responses)
- CloseRequest
3. **Environment Variables**: Add to `.env` (repo root):
```env
CLAIM_TIMEOUT_ENABLED=false
AUTO_UNCLAIM_ENABLED=false
ALLOW_CLAIM_OVERWRITE=false
USE_THREADS=false
```
4. **No Breaking Changes**: All existing functionality preserved
---
## Best Practices
### Tags
- Use descriptive names (e.g., `welcome`, `closing`, `escalation-info`)
- Include variables for personalization
- Keep content concise but helpful
- Review and update regularly
### Priority System
- Set priority early in ticket lifecycle
- Use high priority sparingly
- Review priority regularly
- Consider SLA based on priority
### Panels
- Place in dedicated support channels
- Use clear, welcoming language
- Include instructions
- Monitor for spam/abuse
### Claiming
- Enable auto-unclaim to prevent stale claims
- Set reasonable timeout periods
- Use overwrite cautiously
- Communicate with team about transfers
---
## Troubleshooting
### Commands Not Appearing
- Verify `DISCORD_APPLICATION_ID` is set
- Check bot has application.commands scope
- Wait up to 1 hour for Discord to sync
- Restart bot after .env changes
### Modal Not Showing
- Ensure user has Create Posts permission
- Check for Discord outages
- Verify bot has proper permissions
### Saved Responses Not Working
- Check MongoDB connection and permissions
- Use `/response list` to confirm saved response exists
- Check for errors in saved response content
### Priority Not Updating
- Verify ticket exists in database
- Check PRIORITY_ENABLED is true
- Ensure valid priority value (low/normal/high)
---
## Performance Considerations
### Database
- MongoDB (Mongoose) for all persistent data
- Regular backups recommended
- Run from repo root; `.env` in repo root
### Auto-Checks
- Auto-close: Runs every hour
- Auto-unclaim: Runs every hour
- Reminders: Runs every 30 minutes
- Adjust intervals in code if needed
### Rate Limits
- Channel creation: 50/day per guild
- Channel rename: Discord enforces 2 per 10 minutes per channel **per bot** ([Discord docs](https://discord.com/developers/docs/topics/rate-limits)). Renames route through `utils/renamer.js` (RENAMER_BOT secondary token); on 401/403/429 from the secondary, `services/channelQueue.js` falls back to the primary bot via `channel.setName`.
- Message edits: Be cautious with bulk operations
---
## Future Enhancements
### Planned Features
1. **Statistics Dashboard**: Track ticket metrics
2. **Feedback System**: Collect user ratings
3. **Advanced Automations**: Rule builder UI
4. **Ticket Templates**: Pre-filled forms
5. **SLA Tracking**: Response time monitoring
6. **Multi-language**: Localization support
7. **Web Dashboard**: View tickets in browser
8. **API Endpoints**: External integrations
### Community Requests
- Custom ticket categories per game
- User blacklist system
- Scheduled availability hours
- Ticket assignment rotation
- Knowledge base integration
---
## Support & Contributing
### Getting Help
- Check documentation first
- Review troubleshooting section
- Check logs for error messages
- Test with minimal configuration
### Reporting Bugs
Include:
- Steps to reproduce
- Expected behavior
- Actual behavior
- Environment details
- Log excerpts
### Feature Requests
Consider:
- Use case description
- Priority/importance
- Potential workarounds
- Similar existing features
---
## Changelog
### v2.0.0 - Major Feature Update
**Added:**
- Variables system for dynamic messages
- Tags/saved responses system
- User management commands (/add, /remove)
- Transfer, move, force-close commands
- Close confirmation flow
- Enhanced claiming (overwrite, auto-unclaim)
- Modal forms for ticket creation
- Priority system
- Panel system for Discord tickets
- Thread-style tickets option
- Comprehensive /help command
**Improved:**
- Database schema with new fields
- Permission handling
- Error messages
- Logging
**Fixed:**
- Various edge cases
- Permission issues
- Database constraints
---
*Last Updated: 2025*
*Version: 2.0.0*

View File

@@ -1,72 +0,0 @@
# Broccolini Bot Roadmap & Proposal
Short proposal and possible next steps for the Broccolini Bot ticketing system. Discord + Gmail + MongoDB remain the core; any extension is additive.
---
## Current State
- **Email → Discord:** Gmail poll creates ticket channels/threads; replies sync back to Gmail.
- **Discord-first:** Panels, slash commands, buttons, modals, context menus for full ticket lifecycle (claim, close, escalate, tag, priority, transfer, move, saved responses).
- **MongoDB:** Single data store for tickets, transcripts, tags, close requests, guild settings, and (optional) account info.
- **Automation:** Auto-close, reminders, auto-unclaim, claim timeout; all configurable via `.env`.
- **Security:** HTML escaping in outbound emails; test env workflow; optional healthcheck host binding.
No external ticketing API (e.g. Zammad) is used; the bot is self-contained.
---
## Possible Next Steps
### 1. Read-only API layer
- Expose ticket and metadata via a **read-only** HTTP API (e.g. alongside the bot or a small separate service).
- Endpoints: list/filter tickets, ticket by ID, “my tickets” by Discord ID, tags, guild settings.
- Enables dashboards, mobile tools, or a **Support Cockpit** (bOSScord-style overlay) without changing Discord or bot behavior.
- Optional: use something like Directus on top of MongoDB for instant REST/GraphQL and admin UI.
### 2. Ticket routing & queues
- Derive a **queue** (or routing bucket) per ticket from game detection (`GAME_LIST`), subject/body keywords, and existing tags.
- Store queue on the ticket document; show it in Discord (e.g. in embeds or channel name) and in any future API/UI.
- Enables “Network”, “Billing”, “Mod Help”, “Game X” views without changing how staff use Discord.
### 3. Incident & problem tracking
- **Incident:** one-off “something broke” ticket.
- **Problem:** recurring issue; link multiple incidents, track root cause, workaround, and fix status.
- Optional new commands or buttons to “Link to problem” / “Create problem from ticket” and optional API fields.
### 4. Knowledge base & PM links
- Internal KB (e.g. Wiki.js): link from problems/tickets to articles; optional `/kb search` in Discord.
- PM tool (Plane, Focalboard, Taiga): “Create PM task from ticket” and link ticket ↔ task.
- Broccolini Bot stays the source of truth for tickets; KB and PM are linked data.
### 5. bOSScord / Support Cockpit
- Web (later desktop) client that **reads** from the API and MongoDB.
- Richer views: queues, SLA-style status, “whos viewing this ticket”, virtual display names, links to KB and PM.
- All writes and communication remain in Discord; the client is view/routing only.
### 6. GitHub / GitLab (optional)
- From a problem or PM task: create issue/PR with context.
- Webhooks to update problem/task when issues close or PRs merge.
---
## Principles
- **Discord + Broccolini Bot are canonical.** New features augment, they dont replace, the current flow.
- **API-first for new UIs.** Any dashboard or cockpit consumes a read-only (or narrowly write) API; no direct DB access from frontends.
- **Config and secrets stay in `.env`.** New services get their own env or reuse existing vars where it makes sense.
- **MongoDB remains the primary store.** New collections or fields as needed; no second database unless justified.
---
## No Deadlines
This document is a proposal and idea list. Work can proceed in small steps: e.g. add a read-only ticket API, then add queue derivation, then plug in a simple dashboard or bOSScord.
For a fuller platform vision (bOSScord, queues, incidents/problems, KB, PM), see the parent repos bOSScord proposal if present.

View File

@@ -1,352 +0,0 @@
# 🎉 Discord API Improvements - COMPLETE!
## ✅ All 12 Improvements Successfully Implemented
---
## 🚀 Quick Start
### 1. Restart Your Bot
```bash
npm start
```
### 2. Commands Will Auto-Register
Wait up to 1 hour for Discord to fully sync all commands.
### 3. Try New Features
#### For Staff:
```
/search query:test status:open
/response list
Right-click any message → "Create Ticket From Message"
Right-click any user → "View User Tickets"
```
#### For Admins:
```
/stats
```
#### For Everyone:
Set priority with `/priority` (dropdown: low, normal, medium, high); channel name gets the priority emoji.
---
## 📊 What Changed
### Commands
- **Before:** 15 commands
- **After:** 13 slash commands + 2 context menu commands = 15 total
- Saved responses: `/response send`, `/response create`, etc.; ticket category: `/tag` (dropdown).
### New Features
- ✅ Search command with filters
- ✅ Stats command with analytics
- ✅ Context menu commands (right-click)
- ✅ Priority selection buttons
- ✅ Tag delete confirmation
- ✅ Loading states everywhere
- ✅ Error tracking & monitoring
- ✅ Thread-style tickets support
### Improvements
- ✅ Context restrictions (guild-only commands)
- ✅ Permission checks (staff-only visibility)
- ✅ String length validation (10-500 chars, etc.)
- ✅ Better organization (grouped tag commands)
---
## 🎯 Key New Commands
### `/search <query> [status]`
Search tickets by email, subject, or number.
**Example:**
```
/search query:john@example.com status:open
```
### `/stats`
View bot analytics and performance metrics.
**Shows:**
- Bot uptime
- Total interactions
- Open/closed tickets
- Error rates
- Top commands
### `/response send|create|edit|delete|list` and `/tag`
Saved responses: `/response send`, `/response create`, `/response edit`, `/response delete`, `/response list`. Use `/tag` (dropdown) to set ticket category (Server Down, Billing, Mod Help, etc.); the bot posts a categorization message.
---
## 🖱️ Context Menu Commands
### Create Ticket From Message
1. Right-click any message
2. Apps → "Create Ticket From Message"
3. Ticket created with message content!
### View User Tickets
1. Right-click any user
2. Apps → "View User Tickets"
3. See all their tickets instantly!
---
## 🎨 Priority (slash command only)
Set ticket priority with `/priority` (dropdown: low, normal, medium, high). The channel/thread name is prefixed with the priority emoji (🟢 🟡 🟠 🔴). No priority buttons are shown on tickets; use the command only.
---
## 🧵 Thread-Style Tickets (Optional)
Want tickets as threads instead of channels?
**Enable in `.env`:**
```env
USE_THREADS=true
THREAD_PARENT_CHANNEL=<your_channel_id>
```
**Benefits:**
- Cleaner server structure
- Auto-archive after 24h
- No channel limit issues
- Perfect for high volume
---
## 📈 Analytics & Monitoring
### What's Tracked
- Every command used
- Every button clicked
- Every modal submitted
- Every error that occurs
### View Analytics
```
/stats
```
### Console Output
```
📊 Analytics: commands/search by User#1234
❌ Error tracked: tag-create: UNIQUE constraint failed
⚠️ HIGH ERROR RATE: 6.5% in last hour
```
---
## 🔒 Permission System
### Who Sees What
**Everyone:**
- `/help` (works everywhere including DMs)
**Staff (Manage Messages):**
- `/add`, `/remove`
- `/transfer`
- `/search`
- `/escalate`
- `/deescalate`
- Context menu commands
**Staff (Manage Channels):**
- `/move`
- `/force-close`
- `/panel`
**Administrators:**
- `/stats`
---
## ✨ UX Improvements
### Loading States
Commands show "thinking..." indicator:
- `/search` - While searching database
- `/stats` - While calculating metrics
- `/tag list` - While fetching tags
- Context menus - While processing
### Confirmations
Destructive actions require confirmation:
- **Tag delete:** Shows Yes/Cancel buttons
- **Ticket close:** Shows Confirm/Cancel buttons
### Validation
Better error messages:
- Reason too short? "Must be at least 10 characters"
- Tag name taken? "Tag already exists"
- Channel not found? Clear, actionable message
---
## 📋 Migration Checklist
- [x] Code updated with all improvements
- [x] No breaking changes
- [x] All existing features preserved
- [x] New commands added
- [x] Context menu commands added
- [x] Analytics system integrated
- [x] Error tracking enabled
- [x] Documentation complete
### To Deploy:
1. ✅ Backup database (optional but recommended)
2. ✅ Restart bot: `npm start`
3. ✅ Test new commands
4. ✅ Try context menus
5. ✅ Check `/stats`
---
## 🐛 Known Issues
**None!** All features tested and working.
### If Issues Arise:
1. Check console for error messages
2. Verify bot permissions
3. Wait for command sync (up to 1 hour)
4. Review `DISCORD_API_IMPROVEMENTS.md`
---
## 📚 Documentation
### Created/Updated Files:
1. **DISCORD_API_IMPROVEMENTS.md** - Detailed feature documentation
2. **UPGRADE_COMPLETE.md** - This file (quick reference)
3. **DISCORD_API_VALIDATION.md** - Original validation report
4. **broccolini-discord.js** - Updated with all features
### Read These:
- **QUICKSTART.md** - Getting started guide
- **PHASE_FEATURES.md** - Previous features reference
- **IMPLEMENTATION_SUMMARY.md** - Technical overview
---
## 🎯 Test Plan
### Basic Tests
- [x] Run `/help` - Should work
- [x] Run `/response list` - Shows saved responses
- [x] Run `/stats` - Shows analytics
- [x] Run `/search query:test` - Searches tickets
- [x] Run `/priority` in a ticket channel - Changes priority and renames channel with emoji
- [x] Right-click message - Shows context menu
- [x] Right-click user - Shows context menu
- [x] Try `/response delete` - Shows confirmation
### Staff Commands
- [x] All staff commands only visible to staff
- [x] Regular users can't see them
- [x] Permission checks work
### Analytics
- [x] Console shows interaction tracking
- [x] `/stats` displays metrics
- [x] Error tracking works
---
## 💡 Tips for Your Team
### For Staff
1. Use `/search` to find tickets quickly
2. Right-click messages to create tickets
3. Use `/priority` (dropdown: low, normal, medium, high); channel name is prefixed with the priority emoji
4. Create tags for common responses
### For Admins
1. Check `/stats` daily
2. Monitor error rates
3. Review top commands
4. Identify unused features
### For Everyone
1. Use `/help` to see all commands
2. Commands now grouped (cleaner!)
3. Loading states show bot is working
4. Confirmations prevent accidents
---
## 🏆 Achievement Unlocked!
**100% Discord API Compliance**
**All Best Practices Implemented**
**Professional-Grade Bot**
**Production Ready**
**Stats:**
- 12/12 Improvements Complete
- 800+ Lines of Code Added
- 2 New Context Menu Commands
- 5 /response subcommands (send, create, edit, delete, list)
- Full Analytics System
- Comprehensive Error Tracking
---
## 🚀 What's Next?
**You're done!** All requested features implemented.
**Optional Future Ideas:**
1. Add more context menu commands
2. Build web dashboard
3. Add localization (multiple languages)
4. Create automation rules engine
5. Export analytics to CSV
---
## 📞 Support
### Resources
- Discord API Docs: https://discord.com/developers/docs
- Discord.js Guide: https://discordjs.guide/
- Your documentation files (listed above)
### Questions?
Check:
1. `/help` command in Discord
2. DISCORD_API_IMPROVEMENTS.md
3. Console logs for errors
4. `/stats` for bot health
---
**Version:** 3.0.0
**Release Date:** February 2025
**Status:** Production Ready ✅
---
# 🎊 Congratulations!
Your ticket system is now:
- ✅ Modern
- ✅ Feature-rich
- ✅ Professional
- ✅ Analytics-powered
- ✅ Best-practices compliant
**Enjoy your upgraded bot!** 🚀
---
*P.S. Use `/priority` on a ticket channel to set low, normal, medium, or high the channel name will show the priority emoji.*

View File

@@ -1,13 +0,0 @@
## 13. Prompting: deep analytic experience
**Mode A — single transcript, JSON only**
> “For this single transcript, use **Mode A: Extraction** and respond only with a single JSON object that follows the toplevel structure in the Singletranscript JSON response schema and the field definitions in Transcript analytics schemas (perfield definitions). Follow the General extraction rules. Output only valid JSON.”
**Mode B — single transcript, narrative**
> “Using the schemas in this document (support issue categories, ticket tags, wiki categories and slug patterns, gamespecific topics, game detection, Broccolini team IDs, wiki suggestion & outcome analytics), summarize this ticket transcript. Include account & contact, issue (with game_detected and game_or_server), reproduction, environment, priority & impact, rules/abuse if applicable, suggested wiki slugs, and any staff mentions/requests/sentiment. Note whether any wiki article appeared to solve or not solve the issue, and whether the user wanted Broccolini to do it or was walked through doing it themselves.”
**Mode C — batch analytics**
> “Using **Mode C: Batch analytics** over transcripts in [path] or a list of JSON objects from Mode A, compute perticket and aggregate analytics from this document: issue categories, tags, game_detected and game_or_server distributions, wiki usage and success/failure, staff involvement and wikilinked outcomes, email analytics, frequency/impact distributions, resolution patterns, intake gaps, and all recurring analytics in the Broccolini support section. Output tables and a concise narrative per major dimension.”

View File

@@ -1,97 +0,0 @@
# Game list (Broccolini Bot schema)
Canonical list of games and their **display name**, **key** (snake_case), and **aliases**. Used by `config.js` (`GAME_LIST`, `GAME_ALIASES`, `GAME_NAME_TO_KEY`) and `game-options.json`.
## GAME_LIST (display names, comma-separated)
Use this value for the `GAME_LIST` env var:
```
7 Days to Die, Abiotic Factor, ARK: Survival Evolved, Conan Exiles, Core Keeper, Counter-Strike 2, DayZ, ECO, Enshrouded, Factorio, FiveM, The Front, Garry's Mod, Hytale, ICARUS, Minecraft, Necesse, Palworld, Project Zomboid, Rust, Satisfactory, Sons of the Forest, Soulmask, Star Rupture, Terraria, Valheim, VEIN, Vintage Story, Voyagers of Nera, V Rising
```
## Table (display name, key, aliases)
| Display name | Key | Aliases |
|--------------|-----|--------|
| 7 Days to Die | `7_days_to_die` | `7D2D`, `7 days` |
| Abiotic Factor | `abiotic_factor` | — |
| ARK: Survival Evolved | `ark_survival_evolved` | `Ark` |
| Conan Exiles | `conan_exiles` | — |
| Core Keeper | `core_keeper` | — |
| Counter-Strike 2 | `counter_strike_2` | `CS2` |
| DayZ | `dayz` | — |
| ECO | `eco` | — |
| Enshrouded | `enshrouded` | — |
| Factorio | `factorio` | — |
| FiveM | `fivem` | — |
| The Front | `the_front` | — |
| Garry's Mod | `garrys_mod` | — |
| Hytale | `hytale` | — |
| ICARUS | `icarus` | — |
| Minecraft | `minecraft` | `MC` |
| Necesse | `necesse` | — |
| Palworld | `palworld` | — |
| Project Zomboid | `project_zomboid` | `PZ`, `zomboid` |
| Rust | `rust` | — |
| Satisfactory | `satisfactory` | — |
| Sons of the Forest | `sons_of_the_forest` | `SOTF` |
| Soulmask | `soulmask` | — |
| Star Rupture | `star_rupture` | — |
| Terraria | `terraria` | — |
| Valheim | `valheim` | — |
| VEIN | `vein` | — |
| Vintage Story | `vintage_story` | — |
| Voyagers of Nera | `voyagers_of_nera` | — |
| V Rising | `v_rising` | — |
---
## Matching rules (e.g. 7D2D → 7 Days to Die)
Game detection lives in **`utils.js`** (`detectGame(subject, body)`). It only looks at the **combined subject + body** (lowercased). Matching is **case-insensitive** and uses **word boundaries**.
### 1. Full names first
- **Source:** `GAME_NAMES` (from env `GAME_LIST`, comma-separated, trimmed).
- **How:** For each game name, the code builds a regex: `\b` + escaped name + `\b`, with flag `i`.
- So the **exact display name** must appear as **whole words**.
Examples: “7 days to die” matches → `7 Days to Die`; “zomboid” alone does *not* match “Project Zomboid” (would need “project zomboid” as words).
### 2. Aliases second
- **Source:** `GAME_ALIASES` in `config.js` (alias → full display name).
- **How:** For each alias, the **alias** is lowercased, then the same pattern: `\b` + escaped alias + `\b`, case-insensitive.
- If it matches, the function returns the **full game name** (the value in `GAME_ALIASES`).
- So “7d2d”, “7D2D”, “7 days” match and resolve to **7 Days to Die**; “PZ” / “zomboid” resolve to **Project Zomboid**; “MC” → **Minecraft**; “Ark” → **ARK: Survival Evolved**; “SOTF” → **Sons of the Forest**; “CS2” → **Counter-Strike 2**.
### 3. Word boundaries
- `\b` means “word boundary” (between word and non-word character, or start/end of string).
- So “7d2d” matches “my 7d2d server” or “7D2D” but not “7d2dmod” (no boundary after 7d2d) unless that substring appears as a separate word.
### 4. Order and “first match wins”
- Full names are checked **before** aliases. So if the text contains both a full name and an alias, the full name wins when it matches as whole words.
- First matching game in `GAME_NAMES` or first matching alias in `GAME_ALIASES` wins; no tie-breaking between games.
### 5. No match
- If neither a full name nor an alias matches (with word boundaries), `detectGame` returns **`'Not Mentioned'`**.
### Summary
| Input (in subject/body) | Resolved game |
|-------------------------|----------------|
| 7d2d, 7D2D, 7 days | 7 Days to Die |
| PZ, zomboid | Project Zomboid |
| MC | Minecraft |
| Ark | ARK: Survival Evolved |
| SOTF | Sons of the Forest |
| CS2 | Counter-Strike 2 |
When adding a new game:
1. Add its **display name** to `GAME_LIST` (env) and to `GAME_NAME_TO_KEY` in `config.js`.
2. Add **key → display name** to `game-options.json`.
3. If users might type a shorthand (e.g. 7D2D), add an entry to **`GAME_ALIASES`** in `config.js` mapping that alias to the full display name.

View File

@@ -1,60 +0,0 @@
# Regex detection code and games list
## Regex detection code (utils.js)
```javascript
function escapeRegex(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
const detectGame = (subject, body) => {
const txt = `${subject} ${body}`.toLowerCase();
for (const game of GAME_NAMES) {
const g = game.toLowerCase();
const re = new RegExp(`\\b${escapeRegex(g)}\\b`, 'i');
if (re.test(txt)) return game;
}
for (const [alias, fullName] of Object.entries(GAME_ALIASES)) {
const a = alias.toLowerCase();
const re = new RegExp(`\\b${escapeRegex(a)}\\b`, 'i');
if (re.test(txt)) return fullName;
}
return 'Not Mentioned';
};
```
## Games list
- 7 Days to Die
- Abiotic Factor
- ARK: Survival Evolved
- Conan Exiles
- Core Keeper
- Counter-Strike 2
- DayZ
- ECO
- Enshrouded
- Factorio
- FiveM
- The Front
- Garry's Mod
- Hytale
- ICARUS
- Minecraft
- Necesse
- Palworld
- Project Zomboid
- Rust
- Satisfactory
- Sons of the Forest
- Soulmask
- Star Rupture
- Terraria
- Valheim
- VEIN
- Vintage Story
- Voyagers of Nera
- V Rising

View File

@@ -1,126 +0,0 @@
# 1Password integration (API keys & tokens)
Use 1Password as the single source of truth for Broccolini Bot secrets. Your `.env` file then holds only **Secret References** (`op://...`), so you never store plaintext tokens on disk.
---
## Setup checklist (extension + CLI)
| Step | What to do |
|------|------------|
| **1. Extension** | In Cursor: **Extensions** (Ctrl+Shift+X) → search **1Password****Install**. *(On WSL/Linux the extension may refuse to install; use the CLI path below instead.)* |
| **2. Choose account** | After installing: Command Palette (Ctrl+Shift+P) → **1Password: Choose account** → sign in and pick a vault. |
| **3. CLI** | Install 1Password CLI: `sudo apt install op` (WSL/Linux) or [install from 1Password](https://developer.1password.com/docs/cli/get-started/). Then run **`eval $(op signin)`** once per terminal (or add to your shell profile). |
| **4. Store secrets** | In 1Password: create an item (e.g. **Broccolini Bot**) and add custom fields for each secret (e.g. `DISCORD_TOKEN`, `MONGODB_URI`, `REFRESH_TOKEN`). Use **Copy reference** on each field. |
| **5. .env with refs** | In `broccolini-bot/.env`: put `KEY=op://Vault/ItemName/FIELD` for each secret (no plaintext). Use **1Password: Get from 1Password** in the editor to insert refs, or paste from step 4. |
| **6. Run bot** | From `broccolini-bot/`: **`npm run start:1p`** (or `op run --env-file=.env -- npm run start`). |
If the extension is not available on your OS, use **steps 36 only** (CLI + Secret References in `.env` + `op run`).
---
## Extensions and Cursor integration
| What | Where | Purpose |
|------|--------|--------|
| **1Password for VS Code** | [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=1Password.1password) | Works in Cursor. Save/retrieve secrets, Secret References, detect secrets, preview `op://` refs. [Docs](https://developer.1password.com/docs/vscode/). |
| **1Password Cursor Hooks** | [1Password Marketplace](https://marketplace.1password.com/integration/cursor-hooks) | Validates that **1Password Environments**mounted `.env` files are present and valid before Cursor Agent runs shell commands. [Cursor Hooks docs](https://developer.1password.com/docs/cursor-hooks), [validate hook guide](https://developer.1password.com/docs/environments/cursor-hook-validate). |
- **VS Code extension:** Install in Cursor via Extensions (search “1Password”). Use **1Password: Get from 1Password** / **Save in 1Password** and Secret References in `.env` and code.
- **Cursor Hooks:** Uses **1Password Environments** with *locally mounted* `.env` files (Mac/Linux; requires 1Password app + `sqlite3`). Clone [1Password/cursor-hooks](https://github.com/1Password/cursor-hooks), add the hook to `.cursor/hooks`, and optionally `.1password/environments.toml` to declare which `.env` paths to validate. Then Cursor Agent only runs commands when those env files are mounted.
---
## 1. Install
- **1Password for VS Code / Cursor**
Install the [1Password extension](https://marketplace.visualstudio.com/items?itemName=1Password.1password) in Cursor. You can then use Secret References in files and autofill from your vault.
- **1Password CLI (`op`)**
Required for running the bot with secrets from 1Password. Full install and sign-in: **[Get started with 1Password CLI](https://developer.1password.com/docs/cli/get-started/)**.
- **Install:** Linux (WSL): `sudo apt install op`; Mac: Homebrew or [manual](https://developer.1password.com/docs/cli/get-started/); Windows: winget or manual.
- **Desktop app:** In 1Password app → **Settings****Developer** → turn on **Integrate with 1Password CLI** (Mac: optional Touch ID; Windows: turn on Windows Hello first; Linux: **Settings****Security****Unlock using system authentication**, then Developer → Integrate).
- **Sign in:** Run `eval $(op signin)` (or any `op` command); youll be prompted to authenticate.
---
## 2. Store secrets in 1Password
Create an item that will hold Broccolini Bot env vars, e.g. **“Broccolini Bot”** or **“Broccolini Bot Production”**.
- **Type:** API Credential or Secure Note.
- **Fields:** Add one custom field per secret. Field names must match the env var names (e.g. `DISCORD_TOKEN`, `MONGODB_URI`, `REFRESH_TOKEN`, `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, etc.).
Use the same names as in `.env.example` for the sensitive values.
You can use a second item (e.g. **“Broccolini Bot Test”**) with test-only values and point `.env.test` at it (see below).
---
## 3. Use Secret References in `.env`
In `.env` (and optionally `.env.test`), **do not** put real tokens. Use 1Password Secret References instead:
```bash
# Example: vault "Personal", item "Broccolini Bot", field "password" or custom field name
DISCORD_TOKEN=op://Personal/Broccolini Bot/DISCORD_TOKEN
MONGODB_URI=op://Personal/Broccolini Bot/MONGODB_URI
REFRESH_TOKEN=op://Personal/Broccolini Bot/REFRESH_TOKEN
GOOGLE_CLIENT_ID=op://Personal/Broccolini Bot/GOOGLE_CLIENT_ID
GOOGLE_CLIENT_SECRET=op://Personal/Broccolini Bot/GOOGLE_CLIENT_SECRET
# ... same for other secrets
```
- Replace **Personal** with your vault name.
- Replace **Broccolini Bot** with your item title (spaces are fine).
- The last part is the **field name** inside that item (e.g. `DISCORD_TOKEN`).
Non-secret values (IDs, ports, feature flags) can stay as plain text in `.env` if you prefer.
To get a reference from the 1Password app: open the item → click a field → “Copy reference”.
---
## 4. Run the bot with 1Password
From the **broccolini-bot** directory:
```bash
op run --env-file=.env -- npm run start
```
For the test env:
```bash
op run --env-file=.env.test -- npm run start:test
```
`op run` reads `.env` (or `.env.test`), resolves every `op://...` value with 1Password, and runs the command with the resolved environment. The bots `config.js` still reads from `process.env` as usual; no code changes are required.
---
## 5. npm scripts (already in package.json)
In `broccolini-bot/`:
- `npm run start:1p` — production env from 1Password (resolves `op://` from `.env`)
- `npm run start:test:1p` — test env from 1Password (resolves `op://` from `.env.test`)
---
## 6. Security notes
- **Do not commit `.env` or `.env.test`.** They are gitignored. Even with Secret References, keep them out of the repo.
- **Rotate secrets** in 1Password when needed; no need to edit local files if you only use references.
- The 1Password Cursor extension can fill Secret References in the editor; the CLI is what actually resolves them when you run the bot.
---
## Quick reference
| Task | Command / step |
|------|----------------|
| **CLI get started** | [Get started with 1Password CLI](https://developer.1password.com/docs/cli/get-started/) (install, desktop integration, sign in) |
| Sign in to CLI | `eval $(op signin)` — required so the session is loaded into your shell; plain `op signin` only prints the commands. |
| Run bot (production) | `op run --env-file=.env -- npm run start` |
| Run bot (test) | `op run --env-file=.env.test -- npm run start:test` |
| Copy a secret reference | 1Password app → item → field → “Copy reference” |

View File

@@ -1,73 +0,0 @@
# Environment, Test Env, and Security
## Test environment (prevent data loss)
**.env is production/live.** Changes to `.env` can affect real tickets, Discord, Gmail, and MongoDB. To try config changes safely:
1. **Copy the test template:**
`cp .env.test.example .env.test`
2. **Edit `.env.test`** with test-only values (e.g. test guild, test MongoDB database name, test API URL). Use a separate test DB in `MONGODB_URI` to avoid touching production data.
3. **Run the bot with the test env:**
`npm run start:test`
Or: `ENV_FILE=.env.test node broccolini-discord.js`
4. **Other scripts with test env:**
- `npm run test-mongodb:test` — test MongoDB connection using `.env.test`
5. **After confirming behavior**, migrate only the desired variables from `.env.test` into `.env` (manually). Do not overwrite `.env` blindly.
**Rule:** New or risky env changes are done in `.env.test` first; only after confirmation are they applied to `.env`.
---
## Agent / AI rules
- **Changes to `.env` by an agent (e.g. Cursor) must require explicit user confirmation.** Do not modify `.env` automatically. Prefer proposing changes to `.env.test` or listing the exact edits for the user to apply to `.env`.
- **Do not commit `.env` or `.env.test`.** Only `.env.example` and `.env.test.example` are committed (no secrets).
---
## Security checklist
- **Secrets:** All secrets live in `.env` (or `.env.test` for test). Never commit them. `.gitignore` excludes `.env` and `.env.*` except `.env.example` and `.env.test.example`.
- **Code:** No `eval()` or `new Function()` of user input. No hardcoded tokens, passwords, or API keys in source.
- **Config:** Credentials are read from `process.env` via `config.js`; config is loaded once at startup from the file specified by `ENV_FILE` or default `.env`.
- **MongoDB:** Use a dedicated user and database; bind Mongo to loopback or docker network only; firewall 27017 from public interfaces. For test, use a separate DB or cluster.
- **Discord / Google:** Use tokens with minimal required scopes; rotate if compromised.
- **HTML in emails:** `LOGO_URL`, `EMAIL_SIGNATURE`, and closure messages are escaped in outbound HTML to prevent injection.
- **Healthcheck:** Optional `HEALTHCHECK_HOST=127.0.0.1` in `.env` binds the healthcheck server to localhost only; omit to listen on all interfaces.
- **Dependencies:** Run `npm audit` periodically and fix or accept risk for reported vulnerabilities.
---
## Cleanup and redundancy
- **Single source of truth for env keys:** `.env.example` and `.env.test.example` list all supported variables. Defaults for optional vars live in `config.js`; do not duplicate default values in both `.env.example` and `config.js` for the same value (`.env.example` documents, `config.js` implements).
- **No duplicate env files:** Use `.env` for live, `.env.test` for test; do not commit `.env.local`, `.env.production`, etc. unless documented and gitignored as needed.
- **Parent repo (IB-Discord-Bot):** Broccolini Bot does not reference sibling paths (e.g. `../ngrok`) in code. Run order and ports are documented in `~/IB-Discord-Bot/README.md`.
---
## Connection to IB-Discord-Bot stack
Broccolini Bot is a subproject of **IB-Discord-Bot**. It does not import or require files outside `broccolini-bot/`. Integration is via:
- **Ports:** Broccolini Bot healthcheck uses `DISCORD_ONLY_PORT` (default 5000). Use a different port in `.env.test` (e.g. 5001) if running bot and test bot on the same machine.
See parent **~/IB-Discord-Bot/README.md** for run order, ports, and troubleshooting.
---
## Quick reference
| File / command | Purpose |
|-----------------------|--------|
| `.env` | Live config (never commit). |
| `.env.test` | Test config (never commit). |
| `.env.example` | Template for `.env` (committed). |
| `.env.test.example` | Template for `.env.test` (committed). |
| `ENV_FILE=.env.test` | Load `.env.test` instead of `.env`. |
| `npm run start:test` | Run bot with `.env.test`. |
| `npm run test-mongodb:test` | Test MongoDB using `.env.test`. |

View File

@@ -1,166 +0,0 @@
# MongoDB Setup for Broccolini Bot
## Overview
Broccolini Bot uses **MongoDB only** for persistent storage (tickets, transcripts, counters, tags, close requests). Run all commands from the repo root; create `.env` there (copy from `.env.example`) and set `MONGODB_URI`. For test runs, use `.env.test` (copy from `.env.test.example`) and `npm run test-mongodb:test`; see [ENV_AND_SECURITY.md](./ENV_AND_SECURITY.md).
## Files
1. **`db-connection.js`** - MongoDB connection module with reconnection logic
2. **`models.js`** - Mongoose schemas including:
- `Ticket` - Stores ticket information
- `TicketCounter` - Tracks ticket numbers per sender
- `Transcript` - Stores transcript message references
3. **`scripts/test-mongodb.js`** - Connection test script (run via `npm run test-mongodb`; use `npm run test-mongodb:test` with `.env.test`)
## Configuration
### 1. Environment Variable
Add to your `.env` file:
```env
MONGODB_URI=mongodb://broccoli_bot:CHANGE_ME@localhost:27017/broccoli_db?authSource=broccoli_db
```
**Note:** Mongo runs self-hosted on the same host as the bot. A **dedicated user per database** is required — create `broccoli_bot` with `readWrite` on `broccoli_db` only (no admin/root, no cross-DB access). For test, create a separate user with `readWrite` on `broccoli_db_test` only.
Example mongosh setup:
```javascript
use broccoli_db
db.createUser({
user: "broccoli_bot",
pwd: "CHANGE_ME",
roles: [ { role: "readWrite", db: "broccoli_db" } ]
})
```
Bind Mongo to loopback (`bindIp: 127.0.0.1`) or the internal docker network only; firewall `27017` from public interfaces.
### 2. Install Dependencies
```bash
npm install
```
This will install `mongoose@^6.12.0`.
## Usage in Your Code
### Basic Connection
```javascript
const { connectMongoDB, closeMongoDB, mongoose } = require('./db-connection');
// In your Discord client.once('ready', ...) event:
await connectMongoDB(process.env.MONGODB_URI);
console.log('Connected to MongoDB');
// Get models:
const Ticket = mongoose.model('Ticket');
const TicketCounter = mongoose.model('TicketCounter');
const Transcript = mongoose.model('Transcript');
```
## Schema Reference
### Ticket Schema
```javascript
{
gmail_thread_id: String (required, unique, indexed),
discord_thread_id: String,
broccolini_ticket_id: Number,
sender_email: String (required),
subject: String,
created_at: Date (default: now),
status: String (enum: ['open', 'closed'], default: 'open'),
claimed_by: String (Discord user ID),
escalated: Boolean (default: false),
ticket_number: Number,
rename_count: Number (default: 0), // orphan: no longer read/written (see CLAUDE.md)
rename_window_start: Date // orphan: no longer read/written
}
```
### TicketCounter Schema
```javascript
{
sender_local: String (required, unique),
counter: Number (default: 1)
}
```
### Transcript Schema
```javascript
{
gmail_thread_id: String (required),
transcript_message_id: String,
created_at: Date (default: now)
}
```
## Testing the Connection
From the repo root, run:
```bash
npm run test-mongodb
```
Expected output:
```
Pinged your deployment. You successfully connected to MongoDB!
```
## Graceful Shutdown
Add this to your main file for clean shutdown:
```javascript
process.on('SIGTERM', async () => {
console.log('SIGTERM received, closing connections...');
await closeMongoDB();
await client.destroy(); // Discord client
process.exit(0);
});
process.on('SIGINT', async () => {
console.log('SIGINT received, closing connections...');
await closeMongoDB();
await client.destroy();
process.exit(0);
});
```
## Connection Features
- **Auto-reconnection**: If MongoDB connection drops, Mongoose will automatically attempt to reconnect
- **Connection events**: Logs when connected, disconnected, and reconnected
- **Error handling**: Graceful error messages with stack traces
- **Timeouts**: Configured with reasonable defaults (5s server selection, 45s socket timeout)
## Next Steps
1. Review the schemas in `models.js`
2. Test the connection with `npm run test-mongodb`
3. Start the bot with `npm start` (uses MongoDB throughout)
4. Monitor MongoDB connection in production logs
## Troubleshooting
### Connection refused
- Check MongoDB is running: `docker ps` or `systemctl status mongodb`
- Verify port 27017 is correct in `.env` (or whatever port your mongod is bound to)
- Check MongoDB logs for errors
### Authentication failed
- Verify the user exists in the correct DB's `authSource` (URI must include `?authSource=broccoli_db`)
- Confirm the user has `readWrite` on `broccoli_db`: `db.getUser("broccoli_bot")` in mongosh
### Schema validation errors
- Check required fields are provided when creating documents
- Ensure `status` is either 'open' or 'closed' (enum validation)

View File

@@ -1,154 +0,0 @@
# Project Structure
Overview of the **Broccolini Bot** project layout and the role of each file and directory. Single-level repo: all paths are relative to the repo root.
---
## Root
| File / Dir | Purpose |
|------------|--------|
| `broccolini-discord.js` | **Entry point.** Main Discord bot process. |
| `config.js` | Configuration loading (env, defaults). |
| `db-connection.js` | MongoDB connection setup. |
| `models.js` | Mongoose models (e.g. guild settings, tickets). |
| `utils.js` | Shared utilities. |
| `gmail-poll.js` | Gmail polling / inbox sync logic. |
| `game-options.json` | Game-related options (e.g. for slash commands). |
| `package.json` | Dependencies and npm scripts. |
| `.env.example` | Example environment variables (copy to `.env`). |
| `.env.test.example` | Test env template (copy to `.env.test`; run with `npm run start:test`). See [ENV_AND_SECURITY.md](./ENV_AND_SECURITY.md). |
| `.gitignore` | Git ignore rules (`.env` and `.env.test` never committed). |
---
## Directories
### `commands/`
Slash-command registration and definitions.
| File | Purpose |
|------|--------|
| `register.js` | Registers Discord slash commands (e.g. `/ticket`, `/setup`). |
---
### `handlers/`
Event and interaction handlers for the Discord bot.
| File | Purpose |
|------|--------|
| `accountinfo.js` | Account / user info commands or logic. |
| `analytics.js` | Analytics or stats handling. |
| `buttons.js` | Discord button interaction handlers. |
| `commands.js` | Slash command execution routing. |
| `messages.js` | Message events (e.g. DMs, channel messages). |
| `setup.js` | Setup / configuration flow (e.g. guild setup). |
---
### `services/`
Core business logic and external integrations.
| File | Purpose |
|------|--------|
| `debugLog.js` | Debug / structured logging. |
| `gmail.js` | Gmail API integration (read/send, labels). |
| `guildSettings.js` | Guild-specific settings (DB + cache). |
| `tickets.js` | Ticket lifecycle (create, update, auto-close, reminders). |
---
### `utils/`
Helper modules used across the app.
| File | Purpose |
|------|--------|
| `ticketComponents.js` | Discord components (buttons, selects) for ticket flows. |
---
### `scripts/`
One-off or maintenance scripts.
| File | Purpose |
|------|--------|
| `backup-env.js` | Copies `.env` to `.env.backup` (run via `node scripts/backup-env.js`). |
| `test-mongodb.js` | Tests MongoDB connection (run via `npm run test-mongodb`). |
---
### `docs/`
Documentation and reference files (all paths below relative to repo root).
| File | Purpose |
|------|--------|
| `ENV_AND_SECURITY.md` | Test env workflow, security checklist, agent rules. |
| `QUICKSTART.md` | Quick start / setup guide. |
| `FEATURES_SUMMARY.md` | Feature overview. |
| `IMPLEMENTATION_SUMMARY.md` | Implementation notes. |
| `PHASE_FEATURES.md` | Phased feature list. |
| `MONGODB_SETUP.md` | MongoDB setup instructions. |
| `NEW_FEATURES.md` | New features changelog. |
| `UPGRADE_COMPLETE.md` | Upgrade completion notes. |
| `DISCORD_API_VALIDATION.md` | Discord API validation details. |
| `DISCORD_API_IMPROVEMENTS.md` | Discord API improvements. |
| `PROPOSAL.md` | Roadmap and possible next steps (API, routing, bOSScord). |
| `PROJECT_STRUCTURE.md` | This file. |
---
## Tree View
```
broccolini-bot/
├── broccolini-discord.js # Entry point
├── config.js
├── db-connection.js
├── models.js
├── utils.js
├── gmail-poll.js
├── game-options.json
├── package.json
├── .env.example
├── .gitignore
├── commands/
│ └── register.js
├── handlers/
│ ├── accountinfo.js
│ ├── analytics.js
│ ├── buttons.js
│ ├── commands.js
│ ├── messages.js
│ └── setup.js
├── services/
│ ├── debugLog.js
│ ├── gmail.js
│ ├── guildSettings.js
│ └── tickets.js
├── utils/
│ └── ticketComponents.js
├── scripts/
│ ├── backup-env.js
│ └── test-mongodb.js
├── docs/ # All .md docs except README.md
│ ├── ENV_AND_SECURITY.md
│ ├── QUICKSTART.md
│ ├── MONGODB_SETUP.md
│ └── ... (see table above)
└── README.md
```
---
## Run
- **Start bot:** `npm start` → runs `node broccolini-discord.js`
- **Backup .env:** `node scripts/backup-env.js` → copies `.env` to `.env.backup`
- **Test MongoDB:** `npm run test-mongodb` → runs `node scripts/test-mongodb.js`

View File

@@ -1,199 +0,0 @@
# Broccolini Bot Quick Start Guide
Get started with Broccolini Bot in 5 minutes! Run all commands from the repo root. Ensure `.env` exists in the repo root (copy from `.env.example`).
**Test env:** To try changes safely, use `.env.test` (copy from `.env.test.example`) and run `npm run start:test`. See [ENV_AND_SECURITY.md](./ENV_AND_SECURITY.md). **Agents:** do not modify `.env` without explicit user confirmation; prefer changing `.env.test` first.
## 1. Restart Your Bot
```bash
npm start
```
The bot will automatically:
- Use MongoDB collections (Tag, CloseRequest, etc.) as needed
- Register all new slash commands
- Start background jobs (auto-close, auto-unclaim, reminders)
## 2. Create Your First Saved Response
```
/response create name:welcome content:Welcome to support, {ticket.user}! We'll help you with {ticket.subject}.
```
Then use it:
```
/response send name:welcome
```
Use `/tag` in a ticket channel to set the ticket category (dropdown: Server Down, Billing, Mod Help, etc.). The bot posts: *Your ticket has been categorized as [Emoji][Tag][Emoji].*
## 3. Set Up a Ticket Panel
```
/panel #support-tickets type:both title:Need Help? description:Click below to open a ticket!
```
Use `type` to choose **thread**, **category**, or **both**. Users click the button → Fill out modal → Ticket created automatically!
## 4. Try the New Commands
### User Management
```
/add @user # Add someone to current ticket
/remove @user # Remove someone from ticket
```
### Ticket Actions
```
/transfer @staff # Transfer to another staff member
/move #category # Move to different category
/priority [level] # Set priority: posts upgraded/downgraded/normal message; email sent when set to high
/topic Important! # Set channel topic
/escalate [reason] [tier] # Escalate to tier 2 or 3 (or use Escalate button)
/deescalate # De-escalate one step
/force-close # Close without confirmation
```
### Close Confirmation
Click "Close Ticket" button → Get confirmation prompt → Confirm or cancel
## 5. Configure New Options
Edit your `.env`:
```env
# Enable auto-unclaim after 24 hours of inactivity
AUTO_UNCLAIM_ENABLED=true
AUTO_UNCLAIM_AFTER_HOURS=24
# Allow staff to claim already-claimed tickets
ALLOW_CLAIM_OVERWRITE=true
# Use threads instead of channels (future)
USE_THREADS=false
```
**Restart the bot** after changing `.env`; slash commands may need re-registration (restart the bot).
## 6. Use Variables in Tags
Create smart tags with dynamic content:
```
/response create name:closing content:Thanks {ticket.user}! Ticket #{ticket.number} is now closed. Contact us anytime at {server.name}!
```
Available variables:
- `{ticket.user}`, `{ticket.email}`, `{ticket.number}`, `{ticket.subject}`
- `{staff.name}`, `{staff.mention}`
- `{server.name}`, `{date}`, `{time}`
## 7. Priority Management
Set priorities for better organization:
```
/priority low # 🟢 Low priority
/priority normal # 🟡 Normal (default)
/priority medium # 🟠 Medium priority
/priority high # 🔴 High priority (sends email to ticket sender)
```
The bot posts: *Your ticket has been upgraded/downgraded to [Emoji][Level][Emoji].* or *Your ticket priority has returned to Normal.*
## 8. Test the Panel System
1. Create panel in a channel: `/panel #support`
2. As a user, click "Open Ticket" button
3. Fill out the modal form
4. Submit → Ticket channel created automatically!
## 9. View All Commands
```
/help
```
Shows organized list of all commands with descriptions.
## 10. Check Your Setup
Verify everything is working:
✅ All slash commands appear in Discord
✅ Can create saved responses with `/response create`; use `/tag` for ticket category
✅ Panel shows "Open Ticket" button (and optional type: thread / category / both)
✅ Clicking button shows modal form
✅ Close button shows confirmation
✅ Priority command updates ticket
`/help` command shows all features
---
## Common Issues
### Commands not showing?
- Wait up to 1 hour for Discord to sync
- Verify `DISCORD_APPLICATION_ID` in `.env`
- Restart bot
### Modal not appearing?
- Check user permissions
- Ensure bot has proper guild permissions
- Try in different channel
### Saved responses not working?
- Use `/response list` to see all tags
- Check for typos in tag name
- Autocomplete shows valid tags
---
## Next Steps
1. **Create More Tags**: Add responses for common questions
2. **Set Up Panels**: Put panels in help channels
3. **Train Staff**: Show team the new commands
4. **Enable Auto-Features**: Turn on auto-unclaim if desired
5. **Customize Messages**: Edit `.env` variables for your brand
6. **Monitor Performance**: Check logs for errors
---
## Key Features Summary
**Variables** - Dynamic message templates
🏷️ **Tags** - Saved responses system
👥 **User Management** - Add/remove users from tickets
🎫 **Panel System** - User-friendly ticket creation
📋 **Modal Forms** - Interactive ticket submission
**Priority Levels** - Organize by importance
🔄 **Transfer** - Move tickets between staff
📌 **Enhanced Claiming** - Auto-unclaim, overwrite options
**Close Confirmation** - Prevent accidental closes
📚 **Help Command** - Built-in documentation
---
## Pro Tips
💡 Use variables in welcome messages for personalization
💡 Create tags for FAQs to save time
💡 Set high priority for urgent tickets
💡 Use `/topic` to document ticket status
💡 Enable auto-unclaim to prevent stale claims
💡 Put panels in pinned messages
💡 Use `/transfer` with reasons for context
---
## Getting Help
- Read [PHASE_FEATURES.md](./PHASE_FEATURES.md) for detailed documentation
- Check logs for error messages
- Test features in a test channel first
- Use `/help` in Discord for command reference
---
**Ready to go! Enjoy your enhanced ticket system! 🚀**

View File

@@ -16,11 +16,7 @@
"main": "broccolini-discord.js", "main": "broccolini-discord.js",
"scripts": { "scripts": {
"start": "node broccolini-discord.js", "start": "node broccolini-discord.js",
"start:test": "ENV_FILE=.env.test node broccolini-discord.js", "test-mongodb": "node scripts/test-mongodb.js"
"start:1p": "op run --env-file=.env -- node broccolini-discord.js",
"start:test:1p": "op run --env-file=.env.test -- node broccolini-discord.js",
"test-mongodb": "node scripts/test-mongodb.js",
"test-mongodb:test": "ENV_FILE=.env.test node scripts/test-mongodb.js"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",

View File

@@ -1,11 +1,8 @@
/** /**
* Test MongoDB connection using the native driver. * Test MongoDB connection using the native driver.
* Uses MONGODB_URI from .env (or ENV_FILE when set). Run: npm run test-mongodb * Uses MONGODB_URI from .env. Run: npm run test-mongodb
*/ */
const path = require('path'); require('dotenv').config();
const dotenv = require('dotenv');
const envPath = process.env.ENV_FILE ? path.resolve(process.cwd(), process.env.ENV_FILE) : undefined;
dotenv.config({ path: envPath });
const { MongoClient, ServerApiVersion } = require('mongodb'); const { MongoClient, ServerApiVersion } = require('mongodb');

View File

@@ -3,9 +3,7 @@ const path = require('path');
const { CONFIG } = require('../config'); const { CONFIG } = require('../config');
const { getValidator } = require('./configSchema'); const { getValidator } = require('./configSchema');
const ENV_PATH = process.env.ENV_FILE const ENV_PATH = path.resolve(process.cwd(), '.env');
? path.resolve(process.env.ENV_FILE)
: path.resolve(process.cwd(), '.env');
/** /**
* Serialize a runtime value for .env storage. * Serialize a runtime value for .env storage.

View File

@@ -1,4 +1,4 @@
require('dotenv').config({ path: process.env.ENV_FILE || '../.env' }); require('dotenv').config({ path: '../.env' });
const express = require('express'); const express = require('express');
const session = require('express-session'); const session = require('express-session');
const cookieParser = require('cookie-parser'); const cookieParser = require('cookie-parser');