diff --git a/docs/architecture.md b/docs/architecture.md index 8db3f43..85ec1eb 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -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 diff --git a/docs/commands.md b/docs/commands.md index af2c56a..9589f3d 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -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 ` | View XP rank card | `user` (required): guild member to view | | `/rank reset ` | Reset a user's XP and level | `user` (required) | | `/rank set ` | Set a user's level | `user` (required), `level` (required) | | `/test` | Simple test command | — | @@ -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 diff --git a/docs/configuration.md b/docs/configuration.md index fcff25a..24e41be 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -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 @@ -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`) diff --git a/docs/contributing.md b/docs/contributing.md index 50a2444..f091ff6 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -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 | @@ -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 | diff --git a/docs/database.md b/docs/database.md index eeffb0c..44c78ab 100644 --- a/docs/database.md +++ b/docs/database.md @@ -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`. @@ -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: diff --git a/docs/engineering-guide.md b/docs/engineering-guide.md index 7bef14a..3f6d1f7 100644 --- a/docs/engineering-guide.md +++ b/docs/engineering-guide.md @@ -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 @@ -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`, 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` 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` 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` 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. @@ -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. @@ -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. diff --git a/docs/features.md b/docs/features.md index 1ab197a..3b76fed 100644 --- a/docs/features.md +++ b/docs/features.md @@ -44,6 +44,8 @@ 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 @@ -51,9 +53,14 @@ A configurable support ticket system with HTML transcripts. 3. A private channel is created named `ticket-` 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 @@ -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 @@ -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. --- @@ -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 `** — View a rank card showing current level and XP - **`/rank reset `** — Reset a user's XP and level to defaults - **`/rank set `** — Manually set a user's level @@ -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 diff --git a/docs/getting-started.md b/docs/getting-started.md index 4a72571..3fc7018 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -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 @@ -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