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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,5 @@ This project is currently a WIP!

This is a Discord bot application, not an importable npm module. It starts from `src/index.js`, logs in with the token selected by `src/config.js`, connects to MongoDB through Prisma when enabled, registers commands/components/events, and exposes an unauthenticated health endpoint on `0.0.0.0:8080` that returns a plain-text online message plus Discord invite link.

Developer and operator notes live in [`docs/engineering-guide.md`](docs/engineering-guide.md). Start there for setup constraints, command deployment, permission gates, economy behavior, and troubleshooting.
Start with the [`docs/README.md`](docs/README.md) index for setup, configuration, commands, features, database, architecture, and contribution guides. Developer and operator notes live in [`docs/engineering-guide.md`](docs/engineering-guide.md) for setup constraints, command deployment, permission gates, economy behavior, and troubleshooting.

4 changes: 3 additions & 1 deletion 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
3. **Context menus** — `src/events/ready/registerContextMenus.js` creates missing menus and deletes menus marked `deleted` on `DEV_GUILD_ID`; it does not edit existing menu definitions

## Database Layer

Expand Down Expand Up @@ -176,6 +177,7 @@ The schema files maintain backward-compatible import paths so existing `require(
| `getButtons.js` / `getSelects.js` / `getModals.js` | Load component modules |
| `commandComparing.js` | Normalize command data for diff comparison |
| `normalizeIdAllowlist.js` | Ensure ID lists are arrays |
| `rankCardPresenceStatus.js` | Normalize Discord member presence into canvacord-supported rank-card statuses |
| `buttonPagination.js` | Alternative pagination helper |
| `join-to-create/generateEmbed.js` | JTC status embed builder |
| `join-to-create/generateRow.js` | JTC dashboard button row |
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): must be a member of the guild |
| `/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

The setup command also displays Join-to-Create as a future feature, but it is not currently available in the select menu.

---

## Context Menu Commands
Expand Down
22 changes: 14 additions & 8 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ 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 selected token/client ID/guild ID, with MongoDB URI 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_GUILD_ID` | Guild ID for ready-time slash and context-menu registration | Always |
| `DEV_MONGODB_URI` | MongoDB connection string for development | `PRODUCTION=false` |

### Production Variables
Expand All @@ -34,19 +34,25 @@ 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 is read in `src/example.config.js`, but not every value is selected the same way:

```
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 uses JavaScript truthiness instead of comparing to `"true"`:

```
PRODUCTION=false → DEV_GUILD_ID
PRODUCTION=true → GUILD_ID
PRODUCTION unset or empty → DEV_MONGODB_URI
PRODUCTION=false → MONGODB_URI because the string "false" is truthy
PRODUCTION=true → MONGODB_URI
```

Verify the generated `src/config.js` before starting the bot. For local development with `PRODUCTION=false`, either keep `MONGODB_URI` pointed at a safe development database or adjust `src/config.js` after copying the template.

Ready-time slash and context-menu registration always uses `DEV_GUILD_ID` in `src/events/ready/registerCommands.js` and `src/events/ready/registerContextMenus.js`. `GUILD_ID` feeds `config.handler.guildId`, which is used by developer-command deployment and support-guild features such as stats and welcome handling.

---

## Runtime Configuration (`src/config.js`)
Expand Down
5 changes: 4 additions & 1 deletion docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,11 @@ test("my feature works correctly", () => {
| `dev-command-gate.test.js` | Developer command `options.developers` flag detection |
| `developer-gate.test.js` | Developer ID allowlist validation |
| `prefix-developer-gate.test.js` | Prefix command developer restriction |
| `events-handler-shape.test.js` | Event loader registers `{ event, run }` modules on their declared Discord event names |
| `interaction-cooldown.test.js` | Slash cooldown bookkeeping records one command per first use and expiry timers no-op safely |
| `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` | Historical regression for document-shaped `deleteMany()` deletion failures; current Prisma code should delete through the model delegate |
| `rank-card-presence-status.test.js` | Rank-card presence normalization maps null, missing, and unsupported statuses to canvacord-safe values |
| `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
2 changes: 2 additions & 0 deletions docs/database.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ Join-to-Create voice channel configuration — one per guild.

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

**Operational note:** `src/events/Guild/jointocreate.js` reads `data.UserLimit` and passes it to Discord as the temporary voice channel `userLimit`, but `UserLimit` is not defined in `prisma/schema.prisma`. With the current schema, operators should expect the created channel to use Discord's default user limit unless the schema and setup flow are extended.

## Schema Files

The schema files in `src/schemas/` are thin re-exports of Prisma model delegates:
Expand Down
24 changes: 13 additions & 11 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 registered separately and also listens for slash commands, but it skips interactions that have already been 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 contracts:

- `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.
- `src/handlers/events.js` scans each direct folder under `src/events`.
- The `validations` folder is a special case: every file is called in sequence from one `interactionCreate` listener, which forms the primary interaction validation and execution pipeline.
- Function exports in other folders are grouped under the folder name as the Discord event name. Files under `src/events/ready` use this path.
- Object exports with `{ event, run }` are registered on `eventModule.event` and call `eventModule.run(client, ...args)`. Files under `src/events/Guild` use this path for `messageCreate`, `guildMemberAdd`, `voiceStateUpdate`, and backup `interactionCreate` handling.
- `interactionCreate` currently has multiple listeners: the Guild backup routers and the validations chain. Registration follows the filesystem order returned by `getAllFiles`, so handlers that can overlap should keep their own `interaction.replied` / `interaction.deferred` guards.

## Command Deployment

Expand All @@ -161,19 +163,19 @@ 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 derived `v<package.json version>` tag 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 whether that tag is already present in git history with `git rev-parse "$TAG"`.
4. If the tag exists, the workflow skips all install, test, changelog, and release steps.
5. 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.
- Bump `package.json` before merging when you want a new release tag. The workflow does not create or commit version bumps, and it does not inspect whether the version changed in the latest commit range.
- 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 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.
Expand Down Expand Up @@ -239,4 +241,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`, which is registered from the module's `{ event: "messageCreate", run }` export.
17 changes: 12 additions & 5 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,19 @@ A configurable support ticket system with HTML transcripts.
2. Select **Ticket** from the setup menu
3. Configure:
- **Category** — the channel category where tickets are created
- **Channel** — the channel where the ticket panel is posted
- **Channel** — stored as the ticket panel channel ID
- **Role** — the support role that gets access to tickets

The setup flow stores these values in MongoDB. It does not currently post a ticket panel for you; operators still need to provide a message with a select menu using the `ticket` custom ID before members can open tickets through `ticket-menu.js`.

### How It Works

1. Members select a ticket type from the panel select menu
1. Members select a ticket type from a `ticket` 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

---
Expand Down Expand Up @@ -125,7 +127,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 `JTCSetup` record exists for the guild with a 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 @@ -137,6 +139,8 @@ Temporary voice channels that are created when a user joins a hub channel.

JTC setup data is stored in the `jtcsetups` collection with the hub channel ID, category, and active temporary channels.

The runtime `voiceStateUpdate` handler exists, but `/setup` does not expose Join-to-Create today. The setup command shows JTC as a future feature and its select-menu option is commented out, so current operators need a pre-existing database record or a future setup flow before the handler can run.

---

## Rank / XP System
Expand All @@ -145,7 +149,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 for a guild member
- **`/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 +160,9 @@ Rank cards are generated using the `canvacord` library and display:
- Current level
- XP progress
- Username
- Presence status dot (`online`, `idle`, `dnd`, `offline`, or `streaming`)

If the bot cannot see a member presence, or Discord reports an unsupported status such as `invisible`, the rank card uses `offline` so `canvacord` does not throw. Enable Presence Intent in the Discord Developer Portal if you expect live status dots.

### 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 all runtime dependencies and the Prisma CLI (dev dependency). This repo does not currently define a `postinstall` hook, so generate the Prisma client after installing dependencies and any time `prisma/schema.prisma` changes:

```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` invokes `nodemon`, but `nodemon` is not listed in `package.json`. Install it globally or add it to your local development environment if the command is not available.

### Production mode:

```bash
Expand Down