Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,11 @@ Files in `src/contextmenus/` export `{ data, run }` where `data` includes `type`

## Command Deployment

Two deployment paths exist:
Three deployment paths exist:

1. **Slash commands** — `src/events/ready/registerCommands.js` diffs local commands against Discord API and creates/edits/deletes as needed on `DEV_GUILD_ID`
2. **Developer commands** — `src/handlers/deploy.js` bulk-overwrites guild commands on `config.handler.guildId` using the developer command array
1. **Slash commands** - `src/events/ready/registerCommands.js` diffs local commands against Discord API and creates/edits/deletes as needed on `DEV_GUILD_ID`
2. **Context menus** - `src/events/ready/registerContextMenus.js` creates missing context menus and deletes menus marked `deleted` on `DEV_GUILD_ID`; it does not edit existing menu definitions
3. **Developer commands** - `src/handlers/deploy.js` bulk-overwrites guild commands on `config.handler.guildId` using the developer command array

## Database Layer

Expand Down
4 changes: 3 additions & 1 deletion docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Slash commands are registered per-guild on `DEV_GUILD_ID` at startup.
|---------|-------------|---------|
| `/afk set [message]` | Set your AFK status | `message` (optional): AFK reason |
| `/afk remove` | Remove your AFK status | — |
| `/rank info [user]` | View XP rank card | `user` (optional): defaults to self |
| `/rank info <user>` | View XP rank card | `user` (required): guild member to view |
| `/rank reset <user>` | Reset a user's XP and level | `user` (required) |
| `/rank set <user> <level>` | Set a user's level | `user` (required), `level` (required) |
| `/test` | Simple test command | — |
Expand Down Expand Up @@ -73,6 +73,8 @@ The setup wizard provides a select menu to configure:
- **Welcome System** — channel, message template, rules channel, member/bot auto-roles
- **Ticket System** — category, panel channel, support role

Join-to-Create is listed in the setup embed as a future feature, but it is not currently exposed as a selectable setup option.

---

## Context Menu Commands
Expand Down
32 changes: 23 additions & 9 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ Copy `.env.example` to `.env` and fill in the values.

| Variable | Description | Used When |
|----------|-------------|-----------|
| `PRODUCTION` | Set to `true` for production, `false` for development | Always controls which token/ID/URI set is used |
| `PRODUCTION` | Set to `true` for production, `false` for development | Always - controls token/client/guild selection; see MongoDB caveat below |
| `DEV_TOKEN` | Discord bot token for development | `PRODUCTION=false` |
| `DEV_CLIENT_ID` | Discord application ID for development | `PRODUCTION=false` |
| `DEV_GUILD_ID` | Guild ID for slash command registration | Always |
| `DEV_MONGODB_URI` | MongoDB connection string for development | `PRODUCTION=false` |
| `DEV_GUILD_ID` | Guild ID for ready-time slash and context-menu registration | Always |
| `DEV_MONGODB_URI` | MongoDB connection string for development | Runtime only when `PRODUCTION` is unset/empty in the current template |

### Production Variables

Expand All @@ -34,19 +34,33 @@ Copy `.env.example` to `.env` and fill in the values.

### Production Toggle Behavior

The `PRODUCTION` flag controls which set of credentials the bot uses:
The `PRODUCTION` flag controls most selected credentials with an exact string comparison:

```
PRODUCTION=false DEV_TOKEN, DEV_CLIENT_ID, DEV_MONGODB_URI
PRODUCTION=true CLIENT_TOKEN, CLIENT_ID, MONGODB_URI
PRODUCTION=false -> DEV_TOKEN, DEV_CLIENT_ID, DEV_GUILD_ID
PRODUCTION=true -> CLIENT_TOKEN, CLIENT_ID, GUILD_ID
```

The guild ID for command registration:
MongoDB URI selection in `src/example.config.js` is different:

```
PRODUCTION=false → DEV_GUILD_ID
PRODUCTION=true → GUILD_ID
PRODUCTION unset/empty -> DEV_MONGODB_URI
PRODUCTION=false -> MONGODB_URI
PRODUCTION=true -> MONGODB_URI
```

That happens because `handler.mongodb.uri` checks `process.env.PRODUCTION` for truthiness instead of comparing to `"true"`. If your `.env` contains `PRODUCTION=false`, verify the generated `src/config.js` before starting the bot or change the URI expression locally.

The guild ID for command deployment is split by code path:

```
Ready-time slash commands -> DEV_GUILD_ID
Ready-time context menus -> DEV_GUILD_ID
Developer command deployment -> config.handler.guildId
```

`config.handler.guildId` comes from `src/example.config.js` and follows the exact `PRODUCTION === "true"` check, so developer commands deploy to `GUILD_ID` only when `PRODUCTION=true`.

---

## Runtime Configuration (`src/config.js`)
Expand Down
8 changes: 7 additions & 1 deletion docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ cd Doubt-Discord-Bot
npm install
cp .env.example .env # Fill in credentials
cp src/example.config.js src/config.js # Fill in IDs
npx prisma generate # Required after install/schema changes
npm run dev # Start with nodemon
```

`npm run dev` expects `nodemon` to be available, but this repo does not list `nodemon` in `package.json`. Use `npm start` or install/run nodemon explicitly if the script is unavailable.

## Project Scripts

| Script | Command | Description |
Expand Down Expand Up @@ -204,7 +207,10 @@ test("my feature works correctly", () => {
| `developer-gate.test.js` | Developer ID allowlist validation |
| `prefix-developer-gate.test.js` | Prefix command developer restriction |
| `economy-amount-all.test.js` | Case-insensitive `all` keyword for deposit/withdraw |
| `economy-account-delete.test.js` | Account deletion uses correct deleteMany filter |
| `economy-account-delete.test.js` | Documents the old document-scoped `deleteMany()` failure mode; current code deletes through the Prisma delegate |
| `events-handler-shape.test.js` | Event loader supports `{ event, run }` modules and the separate validation chain |
| `interaction-cooldown.test.js` | Slash cooldown bookkeeping avoids duplicate entries and safe expiry errors |
| `rank-card-presence-status.test.js` | Rank card presence normalization maps unsupported statuses to `offline` |
| `rob-syntax.test.js` | `/rob` source file is valid JavaScript |
| `rob-module-loads.test.js` | `/rob` file parses without errors |
| `rob-cooldown-race.test.js` | Cooldown lock prevents concurrent rob races |
Expand Down
6 changes: 5 additions & 1 deletion docs/database.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ Doubt uses **MongoDB** as its database, accessed through **Prisma v6**. The Pris

## Connection

The Prisma client is initialized in `src/handlers/prisma.js` as a singleton. It reads the MongoDB URI from `config.handler.mongodb.uri` (which resolves to `DEV_MONGODB_URI` or `MONGODB_URI` based on the `PRODUCTION` flag).
The Prisma client is initialized in `src/handlers/prisma.js` as a singleton. It reads the MongoDB URI from `config.handler.mongodb.uri`, not from `DATABASE_URL`.

In the checked-in `src/example.config.js`, token/client/guild selection uses `process.env.PRODUCTION === "true"`, but MongoDB URI selection uses `process.env.PRODUCTION` truthiness. With `PRODUCTION=false` in `.env`, the runtime URI still resolves to `MONGODB_URI`; leave `PRODUCTION` unset/empty for the development URI or adjust `src/config.js` deliberately.

Connection is established during bot startup if `config.handler.mongodb.toggle` is `true`.

Expand Down Expand Up @@ -215,6 +217,8 @@ Join-to-Create voice channel configuration — one per guild.

**Used by:** `voiceStateUpdate` event handler

**Current limitations:** `/setup` does not expose Join-to-Create configuration, so records must already exist for the runtime handler to do anything. `src/events/Guild/jointocreate.js` reads `data.UserLimit`, but `prisma/schema.prisma` does not define a `UserLimit` field on `JTCSetup`, so temporary channels are created without a schema-backed user limit.

## Schema Files

The schema files in `src/schemas/` are thin re-exports of Prisma model delegates:
Expand Down
26 changes: 17 additions & 9 deletions docs/engineering-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,15 +134,17 @@ Command execution contracts:
- The Guild slash-command handler in `src/events/Guild/interactionCreate.js` supports `command.options.cooldown` as a millisecond duration. The cooldown store is an in-memory `Map` keyed by Discord user ID, with command names as values, so it is per-process and clears on restart.
- Slash cooldowns are per user and per command name. A user can be cooling down for one slash command while using another command, and another user is not blocked by the first user's cooldown.
- The slash cooldown is recorded before `command.run(client, interaction)` executes. Expiry uses `setTimeout`; if another timer has already removed the user entry, the expiry handler no-ops instead of throwing.
- The active validation path in `src/events/validations/chatInputCommandValidator.js` calls chat-input commands directly and does not apply the Guild handler cooldown map. Verify the event loader caveat below before depending on `options.cooldown` in production.
- The active validation path in `src/events/validations/chatInputCommandValidator.js` calls chat-input commands directly and does not apply the Guild handler cooldown map. The Guild `interactionCreate` handler is still registered as a backup router, but it skips interactions that were already replied to or deferred.
- Prefix commands are executed through `src/events/Guild/messageCreate.js` with `await command.run(client, message, args)`, so async command failures are caught by that handler's `try/catch` and logged through `log(error, "err")`.
- Prefix command metadata can include `data.permissions` and `data.developers`; `data.cooldown` is present on the prefix eval command but is not enforced by `messageCreate.js`.

Event loader caveat:
Event loader contract:

- `src/handlers/events.js` registers each direct folder under `src/events` as an event name, except `validations`, which is remapped to `interactionCreate`.
- Files under `src/events/ready` and `src/events/validations` export callable functions and match that loader.
- Files under `src/events/Guild` export `{ event, run }` objects and the folder name would register as `Guild`. That shape does not match the current loader's callable function contract or Discord event names such as `messageCreate`, so verify runtime registration before relying on those handlers for prefix commands or component routing.
- Files under `src/events/ready` export callable functions and are grouped under the `ready` event.
- Files under `src/events/validations` export callable functions and run sequentially on a single `interactionCreate` listener.
- Files under `src/events/Guild` export `{ event, run }` objects. The loader registers each module on `eventModule.event` and calls `eventModule.run(client, ...args)`, so handlers such as `messageCreate`, `guildMemberAdd`, and `voiceStateUpdate` are active when events are loaded.
- `tests/events-handler-shape.test.js` locks in object-module registration and the separate validation-chain behavior.

## Command Deployment

Expand All @@ -161,20 +163,20 @@ Troubleshooting command registration:

## GitHub Release Automation

`.github/workflows/release.yml` publishes GitHub Releases from `main` when `package.json` changes the top-level `version` field.
`.github/workflows/release.yml` publishes GitHub Releases from `main` when the release tag for the current `package.json` version does not already exist.

Release workflow behavior:

1. A push to `main` starts the `Release` workflow.
2. The workflow reads `package.json` with Node and derives the release tag as `v<version>`, for example `v1.2.1`.
3. It checks only the latest pushed commit range, `HEAD~1..HEAD`, for a `package.json` line containing `"version"`.
4. If the version changed, it fetches tags and skips the release when the derived tag already exists.
5. If the tag is new, it sets up Node.js `22`, installs dependencies with `npm ci || npm install`, runs `npm test`, generates a changelog from commits since the most recent version-sorted tag, and creates a non-draft, non-prerelease GitHub Release with `softprops/action-gh-release`.
3. It checks the local tag list from a full-depth checkout. If `v<version>` already exists, the workflow skips release creation.
4. If the tag is missing, it sets up Node.js `22`, installs dependencies with `npm ci || npm install`, runs `npm test`, generates a changelog from commits since the most recent version-sorted tag, and creates a non-draft, non-prerelease GitHub Release with `softprops/action-gh-release`.

Release operator notes:

- Bump `package.json` in the commit that lands on `main` when you want a release. The workflow does not create or commit version bumps.
- The workflow creates a Git tag through the GitHub Release action; do not pre-create the same `v<version>` tag unless you intend the workflow to skip release creation.
- The workflow does not inspect whether the latest commit changed the version line. A push to `main` can create a release whenever `v<package.version>` is missing.
- The workflow publishes a GitHub Release only. It does not publish an npm package, build Docker images, deploy the bot, or update Discord commands.
- `contents: write` permission is required so the workflow token can create the release and tag.
- Release creation is gated by `npm test`; keep tests passing before merging a version bump.
Expand Down Expand Up @@ -204,6 +206,12 @@ User-facing economy commands live in `src/commands/slash/Economy/**`:
- `/beg`: randomly chooses a positive or negative wallet change. It updates `Wallet` only if the user has an account, but still sends the result reply when no account exists.
- `/rob user`: requires both users to have economy accounts and the robber to have at least `$100`, and it takes a per-user cooldown lock before database reads. The regression tests document the intended success/failure transfer behavior; verify `rob.js` directly before changing runtime behavior because several past bugs involved cooldown races and uncapped fines.

Rank notes:

- `/rank info`, `/rank reset`, and `/rank set` all require a guild member option named `user`; `/rank info` does not currently default to the caller.
- Rank cards call `Rank#setStatus(rankCardPresenceStatus(member))`. `src/utils/rankCardPresenceStatus.js` passes supported Discord statuses through and maps missing, unknown, or `invisible` statuses to `offline` so canvacord does not throw.
- The rank command reads and writes `Xp` records directly. There is no passive message-XP award handler in the current event set.

`/rob` workflow and constraints:

1. The command checks an in-memory per-user cooldown before reading from MongoDB.
Expand Down Expand Up @@ -239,4 +247,4 @@ When changing economy code, prefer adding or updating focused `node:test` regres
- Prisma/MongoDB connection failures are logged and rethrown from `src/handlers/prisma.js`; `ExtendedClient.start()` attaches a `.catch()` and does not block Discord login while the connection attempt runs. Commands that query MongoDB still depend on a valid runtime URI, network, generated Prisma client, and database credentials.
- Top.gg autoposting only starts when `TOPGG_TOKEN` is present, but the functions module is required during client startup.
- The health endpoint is not authenticated. Do not expose port `8080` publicly unless that is intentional for the hosting environment.
- Prefix command support depends on the `messageCreate` handler in `src/events/Guild/messageCreate.js`; because of the event loader caveat above, verify runtime registration before documenting prefix commands as available to server members.
- Prefix command support depends on both `config.handler.commands.prefix` and the `messageCreate` handler in `src/events/Guild/messageCreate.js`; the handler is registered through the `{ event, run }` object-module path.
18 changes: 14 additions & 4 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,23 @@ A configurable support ticket system with HTML transcripts.
- **Channel** — the channel where the ticket panel is posted
- **Role** — the support role that gets access to tickets

The current setup flow saves those values in MongoDB, but it does not automatically post a ticket panel. A ticket panel or message with a select menu using the `ticket` custom ID must exist before members can open tickets.

### How It Works

1. Members select a ticket type from the panel select menu
2. A modal appears asking for a reason/description
3. A private channel is created named `ticket-<username>`
4. The channel is visible only to the member, support role, and admins
5. When resolved, click the **Close Ticket** button
6. An HTML transcript is generated and DM'd to the ticket creator
6. An HTML transcript is generated and DM'd to the member who clicked **Close Ticket**
7. The channel is deleted after 10 seconds

### Current Constraints

- `ticketSSM.js` stores both `Category` and `Channel`, but `ticket-modal.js` currently uses the stored `Channel` value as the new ticket channel parent.
- The ticket close DM goes to `interaction.member`, not necessarily the original ticket opener.

---

## Welcome System
Expand Down Expand Up @@ -125,7 +132,7 @@ Temporary voice channels that are created when a user joins a hub channel.

### How It Works

1. An admin configures a hub voice channel via the setup system
1. A `jtcsetups` record must already exist for the guild and hub voice channel
2. When a user joins the hub channel, a temporary voice channel is created
3. The channel is named after the user (e.g., `🔊 | Username`)
4. The user gets Manage Channels permission on their channel
Expand All @@ -135,7 +142,7 @@ Temporary voice channels that are created when a user joins a hub channel.

### Configuration

JTC setup data is stored in the `jtcsetups` collection with the hub channel ID, category, and active temporary channels.
JTC setup data is stored in the `jtcsetups` collection with the hub channel ID, category, and active temporary channels. The `/setup` command currently shows Join-to-Create as a future feature and does not expose a JTC setup option.

---

Expand All @@ -145,7 +152,7 @@ Per-guild leveling system with visual rank cards.

### Commands

- **`/rank info [user]`** — View a rank card showing current level and XP
- **`/rank info <user>`** — View a rank card showing current level and XP
- **`/rank reset <user>`** — Reset a user's XP and level to defaults
- **`/rank set <user> <level>`** — Manually set a user's level

Expand All @@ -156,6 +163,9 @@ Rank cards are generated using the `canvacord` library and display:
- Current level
- XP progress
- Username
- Presence status. Missing, unknown, or `invisible` Discord statuses are displayed as `offline` for canvacord compatibility.

There is no passive message-XP award handler in the current event set; rank records are created or changed by rank commands.

### Data Storage

Expand Down
6 changes: 3 additions & 3 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ cd Doubt-Discord-Bot
npm install
```

This installs all runtime dependencies and the Prisma CLI (dev dependency). The Prisma client is auto-generated during install via the `postinstall` hook.

If you need to regenerate the Prisma client manually:
This installs runtime dependencies and the Prisma CLI dev dependency. `package.json` does not define a `postinstall` hook, so generate the Prisma client explicitly after installing dependencies and after any schema change:

```bash
npx prisma generate
Expand Down Expand Up @@ -106,6 +104,8 @@ Set `DEV_MONGODB_URI=mongodb://localhost:27017/doubt-dev` in your `.env`.
npm run dev
```

`npm run dev` runs `nodemon .`, but `nodemon` is not listed in `package.json`. Install it globally, use `npx nodemon .`, or run `npm start` if nodemon is unavailable.

### Production mode:

```bash
Expand Down