Conversation
📝 WalkthroughWalkthroughA complete Node.js/TypeScript Discord bot project is introduced to manage Pterodactyl game servers, featuring slash command handlers, SQLite persistence with AES-256-GCM encryption, Docker containerisation, GitHub Actions CI/CD pipelines, systemd service templates, and comprehensive Jest/MSW test coverage with accompanying architectural documentation. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~65 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
There was a problem hiding this comment.
Actionable comments posted: 13
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (17)
CLAUDE.md-6-6 (1)
6-6:⚠️ Potential issue | 🟡 MinorSpelling... I noticed something.
The official spelling is "Node.js", not "NodeJS". It's a minor thing, but consistency with official naming is... preferable.
📝 Proposed fix
-A Discord Bot built on NodeJS in TypeScript for Pterodactyl server management. Shared infrastructure (helpers, loggers, config, responses) lives in the `bongbot-core` package. +A Discord Bot built on Node.js in TypeScript for Pterodactyl server management. Shared infrastructure (helpers, loggers, config, responses) lives in the `bongbot-core` package.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@CLAUDE.md` at line 6, The text in CLAUDE.md uses the nonstandard "NodeJS"; update the phrasing to the official "Node.js" (replace the token "NodeJS" in the sentence "A Discord Bot built on NodeJS in TypeScript for Pterodactyl server management.") and scan for any other occurrences of "NodeJS" in project docs to ensure consistent official naming..env.example-5-5 (1)
5-5:⚠️ Potential issue | 🟡 MinorMissing trailing newline... and insufficient documentation.
The file lacks a trailing newline, which violates POSIX conventions. More importantly,
ENCRYPTION_KEYhas no guidance on its required format or length. Developers will be confused about what value to provide.📝 Proposed improvements
DISCORD_API_KEY= DISCORD_CHANNEL_ID= `#Pterodactyl` Server Management Config SERVER_DATABASE=pterodactyl.db +# ENCRYPTION_KEY should be a 32-byte (256-bit) hex or base64 string ENCRYPTION_KEY= +🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.env.example at line 5, Add a trailing newline to the .env.example file and add a clear comment above the ENCRYPTION_KEY variable explaining the expected format and length (e.g., whether it should be a 32-byte base64 string, a 64-character hex key, or an environment-specific passphrase), include an example placeholder (not a real secret) and any encoding/usage notes (like "base64-encoded, 32 bytes" or "hex, 64 chars") so developers know what to provide; ensure ENCRYPTION_KEY remains present and the file ends with a newline.tsconfig.json-3-3 (1)
3-3:⚠️ Potential issue | 🟡 MinorSpecify Node.js version compatibility for ES2024 target.
Node.js 24+ is needed for full ES2024 support, though 22 works with partial feature availability. Your package.json has no
.enginesfield to enforce this—worth adding if you intend to support only recent Node.js versions. Not critical, but... better to be explicit about such things.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tsconfig.json` at line 3, Your tsconfig.json sets "target": "ES2024" which requires Node.js 24+ for full support; update project compatibility by either lowering the TS "target" to a Node-supported baseline (e.g., ES2022) or explicitly declare supported Node versions in package.json by adding an "engines" field (e.g., "node": ">=24" or a suitable range). Locate the "target" entry in tsconfig.json and decide whether to change it or add the "engines" entry in package.json so runtime expectations are explicit and consistent.README.md-54-61 (1)
54-61:⚠️ Potential issue | 🟡 Minor...Docker image names are inconsistent with this repository.
The pre-built image references
mirasi/bongbot-developandmirasi/bongbot, but this repository isBongBot-Ptero. The images should likely bemirasi/bongbot-ptero-developandmirasi/bongbot-ptero.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` around lines 54 - 61, Update the Docker image names in the README block that currently use mirasi/bongbot-develop and mirasi/bongbot to the repository-consistent names mirasi/bongbot-ptero-develop and mirasi/bongbot-ptero; edit the two bash examples (Dev Build and Release Build) so the docker run commands reference mirasi/bongbot-ptero-develop:latest and mirasi/bongbot-ptero:latest respectively to match the repo's naming convention.src/commands/pterodactyl/remove_server.ts-16-19 (1)
16-19:⚠️ Potential issue | 🟡 Minor...Success message is sent regardless of whether deletion occurred.
If the server name does not exist in the database, the user still receives "Successfully removed server". Consider returning feedback if nothing was deleted.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/pterodactyl/remove_server.ts` around lines 16 - 19, The success response is always returned for serverName even when nothing was deleted; update the removal flow (the handler that constructs the return object using serverName) to check the database deletion/update result (e.g., the delete call's affectedRows/rowCount or the query result) and return a different message when no record was found/removed (e.g., "No server named **${serverName}** found") while keeping the original success message only when the delete actually removed a row.README.md-70-70 (1)
70-70:⚠️ Potential issue | 🟡 Minor...
SERVER_DATABASEhas a default value but is marked required.The
getConnectionmethod indatabasePool.tsdefaults to'pterodactyl.db'. Either remove the required marker or clarify the intent.🔧 Proposed fix
-| `SERVER_DATABASE` | ✅ | Pterodactyl database file name (default: pterodactyl.db) | +| `SERVER_DATABASE` | ❌ | Pterodactyl database file name (default: pterodactyl.db) |🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` at line 70, The README marks SERVER_DATABASE as required while databasePool.ts's getConnection defaults to 'pterodactyl.db'; update one to be consistent: either remove the "required" indicator for SERVER_DATABASE in the README to reflect the default or change getConnection in databasePool.ts to require the env value and throw/configure an explicit error when SERVER_DATABASE is missing; reference getConnection in databasePool.ts and the SERVER_DATABASE entry in README when making the change.README.md-28-31 (1)
28-31:⚠️ Potential issue | 🟡 Minor...Repository URL mismatch.
The clone URL references
BongBotbut this repository isBongBot-Ptero. Users following these instructions would clone the wrong repository.🔧 Proposed fix
1. **Clone the repository**: ```bash - git clone https://github.com/Mirasii/BongBot.git - cd BongBot + git clone https://github.com/Mirasii/BongBot-Ptero.git + cd BongBot-Ptero ```🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` around lines 28 - 31, Update the repository clone instructions in README.md to use the correct repo name: replace the clone URL referencing "BongBot" with "https://github.com/Mirasii/BongBot-Ptero.git" and update the subsequent cd command to "cd BongBot-Ptero" so both the git clone and directory change match the actual repository name.README.md-104-104 (1)
104-104:⚠️ Potential issue | 🟡 Minor...Test file extension mismatch.
The example uses
.jsextension but the project uses TypeScript (.ts). Additionally, the referenced test fileping.test.jsdoes not appear to exist in this repository.🔧 Proposed fix
# Run specific test file -npm test -- tests/commands/ping.test.js +npm test -- tests/commands/buildCommands.test.ts🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` at line 104, Update the README example command "npm test -- tests/commands/ping.test.js" to reference the actual TypeScript test file used in this repo (replace ping.test.js with the correct .ts test path/name) and ensure the test file exists under tests/commands (e.g., update to tests/commands/ping.test.ts or the real filename); verify the updated command matches the project's test runner configuration and file naming conventions so the example runs correctly.src/commands/pterodactyl/list_servers.ts-19-29 (1)
19-29:⚠️ Potential issue | 🟡 MinorInconsistent
ephemeralflag between branches....How careless. When the user has servers, the response is ephemeral. But when they have none, it's public. This inconsistency is likely unintentional. Both responses should probably be ephemeral for consistency.
🔧 Proposed fix
if (servers.length === 0) { embed.setDescription('You have no registered servers.'); - return { embeds: [embed] }; + return { embeds: [embed], ephemeral: true }; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/pterodactyl/list_servers.ts` around lines 19 - 29, The response for the "no servers" branch currently returns { embeds: [embed] } without ephemeral while the "has servers" branch returns { embeds: [embed], ephemeral: true }, causing inconsistent visibility; update the "no servers" return to include ephemeral: true so both branches return { embeds: [embed], ephemeral: true } (locate and change the return in the block that checks servers.length === 0 which builds the embed and calls return).esbuild.config.mjs-21-22 (1)
21-22:⚠️ Potential issue | 🟡 Minor
--minifycurrently does nothing.Line 22 parses the flag, but Line 36 hard-codes
minify: true, so watch/debug builds are minified regardless. Wire the option through instead.Suggested patch
- minify: true, + minify,Also applies to: 24-37
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@esbuild.config.mjs` around lines 21 - 22, The build config is reading the --minify flag into the minify variable but ignores it by hard-coding minify: true in the esbuild options; update the esbuild build invocation (the options object referenced by isWatch and where minify is currently hard-coded) to use the parsed minify variable instead of true so CLI --minify controls whether output is minified (also review related options like sourcemap if they were toggled for debug/watch builds and ensure they respect isWatch/minify as intended)..github/workflows/deploy.yml-33-42 (1)
33-42:⚠️ Potential issue | 🟡 MinorUse
filesinstead offilefor Codecov v5.The
fileparameter is deprecated incodecov/codecov-action@v5. Whilst it technically still exists, you ought to migrate tofilesto avoid future compatibility issues. Simply change line 37 as suggested.Suggested patch
- file: ./coverage/lcov.info + files: ./coverage/lcov.info🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/deploy.yml around lines 33 - 42, The workflow step "Upload coverage reports to Codecov" is using the deprecated `file` input for codecov/codecov-action@v5; update that step (the "Upload coverage reports to Codecov" job using codecov/codecov-action@v5) to use `files` instead, passing the coverage artifact path (e.g. ./coverage/lcov.info) as the new `files` input (or an array of paths) and remove/replace the `file` key so the action uses the supported parameter.src/helpers/database.ts-138-146 (1)
138-146:⚠️ Potential issue | 🟡 MinorMissing ENCRYPTION_KEY validation.
...You're using
process.env.ENCRYPTION_KEY!with a non-null assertion. If someone deploys without setting this variable, they'll get a cryptic error fromBuffer.from. A clear error message would be more helpful.🛡️ Add validation
private encryptApiKey(plaintext: string): string { + if (!process.env.ENCRYPTION_KEY) { + throw new Error('ENCRYPTION_KEY environment variable is not set'); + } const key = Buffer.from(process.env.ENCRYPTION_KEY!, 'hex');Or validate once in the constructor.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/helpers/database.ts` around lines 138 - 146, encryptApiKey currently uses process.env.ENCRYPTION_KEY! without validation which causes cryptic runtime errors; validate the environment variable (process.env.ENCRYPTION_KEY) early (e.g., in the class constructor) ensuring it's present and a valid 32-byte hex string (64 hex chars) for AES-256-GCM, throw a clear Error if missing/invalid, parse it once into a Buffer and store it on the instance (use that buffer in encryptApiKey instead of calling Buffer.from inline), and ensure any related methods (e.g., decrypt counterpart) use the same validated key property..github/workflows/deploy-develop.yml-38-47 (1)
38-47:⚠️ Potential issue | 🟡 Minor...Update required.
The static analysis tool correctly identified that
actions/github-script@v6is outdated. Update tov7for continued runner compatibility.🔧 Suggested fix
- name: Delete Previous Comment if: steps.find-comment.outputs.comment-id != '' - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: |🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/deploy-develop.yml around lines 38 - 47, Update the GitHub Actions step named "Delete Previous Comment" to use the newer github-script action version by changing the uses reference from actions/github-script@v6 to actions/github-script@v7; locate the step with the "uses: actions/github-script@v6" line in the "Delete Previous Comment" step and update it to "@v7" so the runner uses the supported action version.src/commands/pterodactyl/server_status.ts-82-90 (1)
82-90:⚠️ Potential issue | 🟡 Minor...Validate before parsing.
parseInt(dbServerId)at line 82 will returnNaNifdbServerIdis malformed. The subsequentgetServerByIdcall would then fail unpredictably.🛡️ Suggested validation
- const dbServer = this.db.getServerById(parseInt(dbServerId)); + const parsedId = parseInt(dbServerId, 10); + if (isNaN(parsedId)) { + await componentInteraction.followUp({ + content: '❌ Invalid server identifier.', + ephemeral: true, + }); + return; + } + const dbServer = this.db.getServerById(parsedId);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/pterodactyl/server_status.ts` around lines 82 - 90, The code calls this.db.getServerById(parseInt(dbServerId)) without validating dbServerId first, which can produce NaN and cause unpredictable failures; update the handler in server_status.ts to validate dbServerId (e.g., ensure it's a non-empty string or number and that parseInt(dbServerId, 10) yields a finite integer using Number.isInteger or Number.isFinite) before calling this.db.getServerById, and if validation fails send the same ephemeral error via componentInteraction.followUp ('❌ Server configuration not found.' or a clearer message) and return early; reference the parseInt(dbServerId) call, this.db.getServerById, and componentInteraction.followUp to locate and modify the logic.src/commands/pterodactyl/server_status.ts-44-46 (1)
44-46:⚠️ Potential issue | 🟡 Minor...Non-null assertion is risky.
Line 46 uses
selectedServer.id!assumingidis always defined. If the database layer can return servers without anid, this will cause runtime issues.Consider validating
selectedServer.idexists before using it, or ensuring the database type guaranteesidis always present.🛡️ Suggested validation
+ if (selectedServer.id === undefined) { + throw new Error('Server configuration is invalid. Please re-register the server.'); + } + const servers = await fetchServers(this.caller, selectedServer.serverUrl, selectedServer.apiKey); const resources = await fetchAllServerResources(this.caller, servers, selectedServer.serverUrl, selectedServer.apiKey); const embed = buildServerStatusEmbed(servers, resources); - const components = buildServerControlComponents(servers, resources, selectedServer.id!); + const components = buildServerControlComponents(servers, resources, selectedServer.id);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/pterodactyl/server_status.ts` around lines 44 - 46, selectedServer.id is being force-unwrapped with a non-null assertion when passed into buildServerControlComponents, which can crash if id is undefined; update the code to validate selectedServer.id exists before using it (e.g., check if selectedServer and selectedServer.id are defined), and either early-return/send an error message or pass a safe fallback to buildServerControlComponents; ensure the check is placed right before the call that uses selectedServer.id and reference the selectedServer variable and the buildServerControlComponents(...) call when making the change..github/workflows/deploy-develop.yml-30-36 (1)
30-36:⚠️ Potential issue | 🟡 Minor...This filter is too broad.
Line 36 uses
body-includes: ''which matches any comment fromgithub-actions[bot]. If other workflows also comment on PRs, this could delete unrelated comments.Consider adding a unique identifier to your comments and filtering by it.
🔧 Suggested approach
- name: Find Previous Comment id: find-comment uses: peter-evans/find-comment@v3 with: issue-number: ${{ github.event.pull_request.number }} comment-author: 'github-actions[bot]' - body-includes: '' + body-includes: '<!-- bongbot-ptero-develop-comment -->'Then add
<!-- bongbot-ptero-develop-comment -->to the comment body in the create-comment step.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/deploy-develop.yml around lines 30 - 36, The find-comment step is matching any github-actions[bot] comment because body-includes is empty; restrict it by adding a unique identifier string (e.g. <!-- bongbot-ptero-develop-comment -->) to the comment body when you create the comment and then set the find-comment input body-includes to that exact identifier so the step (id: find-comment, uses: peter-evans/find-comment@v3) only finds the specific comment to update/delete.tests/commands/pterodactyl/server_status.test.ts-51-53 (1)
51-53:⚠️ Potential issue | 🟡 Minor...This placement is problematic.
server.listen()at line 53 is called at module load time, outside any test lifecycle hook. This can cause issues with test isolation. Move it tobeforeAll.🔧 Suggested fix
const server = setupServer(...handlers); -server.listen({ onUnhandledRequest: 'bypass' }); +// Move to beforeAll in the describe blockThen in the test suite:
beforeAll(() => { + server.listen({ onUnhandledRequest: 'bypass' }); jest.useFakeTimers(); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/commands/pterodactyl/server_status.test.ts` around lines 51 - 53, The test currently calls server.listen() at module load time which breaks test isolation; move the server.listen({ onUnhandledRequest: 'bypass' }) call into a beforeAll hook so the mock server (created via setupServer(...handlers) assigned to server) starts during test setup and ensure you stop it in afterAll (and optionally reset handlers in afterEach) to mirror the server lifecycle.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/deploy-develop.yml:
- Around line 294-310: Change the SSH action to a fixed release and forward
BRANCH_NAME into the remote session: replace uses: appleboy/ssh-action@master
with uses: appleboy/ssh-action@v1.2.5, and under the action inputs add the envs
mapping so BRANCH_NAME is forwarded (so the script that references
bongbot-ptero-develop@${{ env.BRANCH_NAME }} receives the branch value); ensure
the BRANCH_NAME value is still derived from ${{ github.head_ref }} when setting
the forwarded envs.
In @.github/workflows/deploy.yml:
- Around line 157-163: The workflow step using the appleboy/ssh-action is pinned
to a moving ref (`appleboy/ssh-action@master`) which is unsafe for production;
update the "SSH and Restart Service" step to reference a fixed release tag or
full commit SHA (e.g., replace `appleboy/ssh-action@master` with a specific tag
like `appleboy/ssh-action@v0.x.y` or a commit SHA) so the ssh-action invocation
is immutable and cannot change unexpectedly.
- Around line 110-118: Replace the DRY_RUN prediction step with a real
tag-creation run (or add a second non-dry-run step) so the workflow captures the
actual tag output and then use that output for the Docker push and GitHub
Release; specifically update the step with id get_version
(anothrNick/github-tag-action@1.75.0) to run without DRY_RUN or add a follow-up
step that runs without DRY_RUN and reads the created tag from the action output
(e.g. steps.get_version.outputs.<tag_output_name> or the non-dry-run step's
outputs) and wire that single output into the Docker image tag and the GitHub
Release action so all subsequent steps reference the actual created tag and the
deployment steps are effectively serialized.
In @.github/workflows/docker-vuln-pr.yml:
- Around line 23-29: The "Scan Docker image" step currently uses image-ref: ${{
secrets.DOCKERHUB_USERNAME }}/bongbot-ptero:latest which will usually pull the
previously published latest image; instead, build and tag the image for the
current commit and scan that artifact (or only update the baseline after
deploy). Concretely: add or reuse docker/build-push-action to build and tag the
image with a unique tag (e.g. ${{ github.sha }}), reference that tag in the
trivy step (replace image-ref with ${{ secrets.DOCKERHUB_USERNAME
}}/bongbot-ptero:${{ github.sha }} or the local build output), and ensure the
trivy action (aquasecurity/trivy-action@master) runs after the build so it scans
the image from the current commit rather than :latest.
- Around line 24-29: Replace the floating reference
aquasecurity/trivy-action@master with a fixed, immutable release tag or full
commit SHA to prevent unexpected behavior; update both occurrences of the action
reference (the lines containing "uses: aquasecurity/trivy-action@master") to a
specific tag/sha (e.g., aquasecurity/trivy-action@vX.Y.Z or @<commit-sha>) and
commit the change so the Trivy workflow uses a pinned version.
In @.github/workflows/security-scan.yml:
- Around line 32-33: Replace the mutable action reference
aquasecurity/trivy-action@master with a pinned, immutable reference to a known
safe release (for example aquasecurity/trivy-action@v0.35.0) or a specific
commit SHA (e.g., 57a97c7) wherever the workflow uses that action (the uses:
aquasecurity/trivy-action@master entries); update both occurrences to the chosen
pinned tag/SHA to prevent supply-chain risk.
- Line 60: The workflow uses the wrong image name in the image-ref entry
(currently set to "${{ secrets.DOCKERHUB_USERNAME }}/bongbot:latest"); change it
to the built image name "${{ secrets.DOCKERHUB_USERNAME }}/bongbot-ptero:latest"
so the security-scan workflow scans the same artifact produced by the
build/deploy pipeline and matches the docker-vuln-pr.yml convention.
In `@esbuild.config.mjs`:
- Around line 5-18: The nativeModulePlugin's onLoad currently embeds the
absolute build-time args.path and uses CommonJS module.exports, which breaks at
runtime; update nativeModulePlugin.setup -> build.onLoad to compute the binding
path relative to import.meta.url (not the absolute args.path) and point it to
the copied runtime location (e.g., dist/build/Release/<name>.node), and emit
ESM-compatible code (use export default or create an ESM-compatible require via
createRequire and export the required binding) instead of using module.exports;
locate the logic in nativeModulePlugin and the onLoad handler where args.path
and loader: "js" are used and change the generated contents to resolve via
import.meta.url and the dist/build/Release path and export the native module as
an ES module.
In `@src/commands/pterodactyl/register_server.ts`:
- Around line 15-29: The code currently passes user-supplied serverUrl directly
into fetchServers, creating an SSRF risk; before calling fetchServers in
register_server.ts, parse and validate
interaction.options.getString('server_url') (serverUrl) with the URL
constructor, ensure the protocol is either http or https, strip credentials, and
then resolve the hostname to an IP and reject loopback (127.0.0.0/8, ::1),
link-local and private ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16,
fc00::/7, fe80::/10) and any other non-public addresses; only call
fetchServers(this.caller, serverUrl, apiKey) if validation passes, otherwise
throw a clear error.
In `@src/commands/pterodactyl/server_status.ts`:
- Around line 249-253: The setInterval callback calling checkStatus is
unprotected against thrown/rejected async errors; wrap the async callback in a
try/catch that catches errors from checkStatus, logs or surface the error, and
clears pollInterval on fatal failure to avoid leaving the interval running;
specifically update the setInterval callback that references pollInterval,
checkStatus, and interval to use try { const done = await checkStatus(); if
(done) clearInterval(pollInterval); } catch (err) { clearInterval(pollInterval);
/* log err via your logger or emit an event */ }.
In `@src/index.ts`:
- Around line 62-70: The deletion loop currently drops delete promises by using
botMessages.forEach(... message.delete()), so deletion errors escape the
try/catch and the new card may be posted before old deletions complete; collect
the delete promises from botMessages.map(m => m.delete()) and await
Promise.allSettled(...) (or Promise.all if you prefer failing fast) before
proceeding to send the new card, referencing the variables/messages.fetch result
and the botMessages collection and the message.delete() calls to locate the
change.
- Around line 40-42: The catch block currently always calls interaction.followUp
which will throw if the interaction was never acknowledged (e.g., deferReply
failed); update the error handling in the try/catch around deferReply/handler to
check interaction.deferred || interaction.replied before using followUp and fall
back to interaction.reply when neither is true, and ensure you pass
buildUnknownError(error) as the reply payload; adjust references inside the same
block (interaction.replied, interaction.deferred, buildUnknownError, followUp,
reply) to avoid InteractionNotRepliedError.
In `@tests/commands/pterodactyl/server_status.test.ts`:
- Around line 101-108: Replace the generic fake timer setup in the test
lifecycle so MSW's fetch isn't broken: in the beforeAll where
jest.useFakeTimers() is called, pass the option to not fake queueMicrotask
(jest.useFakeTimers({ doNotFake: ['queueMicrotask'] })); keep the afterAll
cleanup calling jest.useRealTimers() and server.close() as is so timers and the
test server are restored correctly (refer to beforeAll, afterAll,
jest.useFakeTimers and jest.useRealTimers to locate the changes).
---
Minor comments:
In @.env.example:
- Line 5: Add a trailing newline to the .env.example file and add a clear
comment above the ENCRYPTION_KEY variable explaining the expected format and
length (e.g., whether it should be a 32-byte base64 string, a 64-character hex
key, or an environment-specific passphrase), include an example placeholder (not
a real secret) and any encoding/usage notes (like "base64-encoded, 32 bytes" or
"hex, 64 chars") so developers know what to provide; ensure ENCRYPTION_KEY
remains present and the file ends with a newline.
In @.github/workflows/deploy-develop.yml:
- Around line 38-47: Update the GitHub Actions step named "Delete Previous
Comment" to use the newer github-script action version by changing the uses
reference from actions/github-script@v6 to actions/github-script@v7; locate the
step with the "uses: actions/github-script@v6" line in the "Delete Previous
Comment" step and update it to "@v7" so the runner uses the supported action
version.
- Around line 30-36: The find-comment step is matching any github-actions[bot]
comment because body-includes is empty; restrict it by adding a unique
identifier string (e.g. <!-- bongbot-ptero-develop-comment -->) to the comment
body when you create the comment and then set the find-comment input
body-includes to that exact identifier so the step (id: find-comment, uses:
peter-evans/find-comment@v3) only finds the specific comment to update/delete.
In @.github/workflows/deploy.yml:
- Around line 33-42: The workflow step "Upload coverage reports to Codecov" is
using the deprecated `file` input for codecov/codecov-action@v5; update that
step (the "Upload coverage reports to Codecov" job using
codecov/codecov-action@v5) to use `files` instead, passing the coverage artifact
path (e.g. ./coverage/lcov.info) as the new `files` input (or an array of paths)
and remove/replace the `file` key so the action uses the supported parameter.
In `@CLAUDE.md`:
- Line 6: The text in CLAUDE.md uses the nonstandard "NodeJS"; update the
phrasing to the official "Node.js" (replace the token "NodeJS" in the sentence
"A Discord Bot built on NodeJS in TypeScript for Pterodactyl server
management.") and scan for any other occurrences of "NodeJS" in project docs to
ensure consistent official naming.
In `@esbuild.config.mjs`:
- Around line 21-22: The build config is reading the --minify flag into the
minify variable but ignores it by hard-coding minify: true in the esbuild
options; update the esbuild build invocation (the options object referenced by
isWatch and where minify is currently hard-coded) to use the parsed minify
variable instead of true so CLI --minify controls whether output is minified
(also review related options like sourcemap if they were toggled for debug/watch
builds and ensure they respect isWatch/minify as intended).
In `@README.md`:
- Around line 54-61: Update the Docker image names in the README block that
currently use mirasi/bongbot-develop and mirasi/bongbot to the
repository-consistent names mirasi/bongbot-ptero-develop and
mirasi/bongbot-ptero; edit the two bash examples (Dev Build and Release Build)
so the docker run commands reference mirasi/bongbot-ptero-develop:latest and
mirasi/bongbot-ptero:latest respectively to match the repo's naming convention.
- Line 70: The README marks SERVER_DATABASE as required while databasePool.ts's
getConnection defaults to 'pterodactyl.db'; update one to be consistent: either
remove the "required" indicator for SERVER_DATABASE in the README to reflect the
default or change getConnection in databasePool.ts to require the env value and
throw/configure an explicit error when SERVER_DATABASE is missing; reference
getConnection in databasePool.ts and the SERVER_DATABASE entry in README when
making the change.
- Around line 28-31: Update the repository clone instructions in README.md to
use the correct repo name: replace the clone URL referencing "BongBot" with
"https://github.com/Mirasii/BongBot-Ptero.git" and update the subsequent cd
command to "cd BongBot-Ptero" so both the git clone and directory change match
the actual repository name.
- Line 104: Update the README example command "npm test --
tests/commands/ping.test.js" to reference the actual TypeScript test file used
in this repo (replace ping.test.js with the correct .ts test path/name) and
ensure the test file exists under tests/commands (e.g., update to
tests/commands/ping.test.ts or the real filename); verify the updated command
matches the project's test runner configuration and file naming conventions so
the example runs correctly.
In `@src/commands/pterodactyl/list_servers.ts`:
- Around line 19-29: The response for the "no servers" branch currently returns
{ embeds: [embed] } without ephemeral while the "has servers" branch returns {
embeds: [embed], ephemeral: true }, causing inconsistent visibility; update the
"no servers" return to include ephemeral: true so both branches return { embeds:
[embed], ephemeral: true } (locate and change the return in the block that
checks servers.length === 0 which builds the embed and calls return).
In `@src/commands/pterodactyl/remove_server.ts`:
- Around line 16-19: The success response is always returned for serverName even
when nothing was deleted; update the removal flow (the handler that constructs
the return object using serverName) to check the database deletion/update result
(e.g., the delete call's affectedRows/rowCount or the query result) and return a
different message when no record was found/removed (e.g., "No server named
**${serverName}** found") while keeping the original success message only when
the delete actually removed a row.
In `@src/commands/pterodactyl/server_status.ts`:
- Around line 82-90: The code calls this.db.getServerById(parseInt(dbServerId))
without validating dbServerId first, which can produce NaN and cause
unpredictable failures; update the handler in server_status.ts to validate
dbServerId (e.g., ensure it's a non-empty string or number and that
parseInt(dbServerId, 10) yields a finite integer using Number.isInteger or
Number.isFinite) before calling this.db.getServerById, and if validation fails
send the same ephemeral error via componentInteraction.followUp ('❌ Server
configuration not found.' or a clearer message) and return early; reference the
parseInt(dbServerId) call, this.db.getServerById, and
componentInteraction.followUp to locate and modify the logic.
- Around line 44-46: selectedServer.id is being force-unwrapped with a non-null
assertion when passed into buildServerControlComponents, which can crash if id
is undefined; update the code to validate selectedServer.id exists before using
it (e.g., check if selectedServer and selectedServer.id are defined), and either
early-return/send an error message or pass a safe fallback to
buildServerControlComponents; ensure the check is placed right before the call
that uses selectedServer.id and reference the selectedServer variable and the
buildServerControlComponents(...) call when making the change.
In `@src/helpers/database.ts`:
- Around line 138-146: encryptApiKey currently uses process.env.ENCRYPTION_KEY!
without validation which causes cryptic runtime errors; validate the environment
variable (process.env.ENCRYPTION_KEY) early (e.g., in the class constructor)
ensuring it's present and a valid 32-byte hex string (64 hex chars) for
AES-256-GCM, throw a clear Error if missing/invalid, parse it once into a Buffer
and store it on the instance (use that buffer in encryptApiKey instead of
calling Buffer.from inline), and ensure any related methods (e.g., decrypt
counterpart) use the same validated key property.
In `@tests/commands/pterodactyl/server_status.test.ts`:
- Around line 51-53: The test currently calls server.listen() at module load
time which breaks test isolation; move the server.listen({ onUnhandledRequest:
'bypass' }) call into a beforeAll hook so the mock server (created via
setupServer(...handlers) assigned to server) starts during test setup and ensure
you stop it in afterAll (and optionally reset handlers in afterEach) to mirror
the server lifecycle.
In `@tsconfig.json`:
- Line 3: Your tsconfig.json sets "target": "ES2024" which requires Node.js 24+
for full support; update project compatibility by either lowering the TS
"target" to a Node-supported baseline (e.g., ES2022) or explicitly declare
supported Node versions in package.json by adding an "engines" field (e.g.,
"node": ">=24" or a suitable range). Locate the "target" entry in tsconfig.json
and decide whether to change it or add the "engines" entry in package.json so
runtime expectations are explicit and consistent.
---
Nitpick comments:
In @.github/dependabot.yml:
- Around line 6-13: Update the Dependabot configuration in
.github/dependabot.yml to also include the github-actions package-ecosystem so
workflow files are scanned (add an entry with package-ecosystem:
"github-actions" and directory: "/.github/workflows" and same schedule), and
adjust the labels array (currently only "patch") to reflect intended semantics
(e.g., use multiple labels like "patch", "minor", "major" or remove the blanket
"patch") so dependency PRs are labeled correctly; modify the existing npm entry
(package-ecosystem: "npm") only to change labels if needed and keep schedule
unchanged.
In @.gitignore:
- Around line 7-12: The .gitignore has inconsistent directory patterns (e.g.,
coverage/* and dist/* vs data/* and logs/*) and is missing the
test-results/junit.xml entry mentioned in the review; decide whether you want to
ignore directory contents but allow tracking an empty directory placeholder (use
trailing /*) or ignore the entire directory (omit /*), then make the .gitignore
consistent by updating entries for coverage, dist, data, logs to follow that
choice and add test-results/junit.xml and any other missing artefacts like
report.json or .env as needed to match the intended behavior; reference the
existing patterns (coverage/*, dist/*, data/*, logs/*, .env, report.json) when
making the change.
- Around line 3-5: Remove the redundant node_modules entry by keeping the
canonical "node_modules" rule and deleting the "./node_modules/*" duplicate;
leave other entries (e.g., "BongBot.env") as-is so the directory and its
contents are still ignored without duplication.
In `@CLAUDE.md`:
- Line 19: The wording "Object-Orientated" in the CLAUDE.md text should be
changed to the standard technical term "Object-Oriented"; locate the phrase
"Object-Orientated" (e.g., in the sentence "Functional components with
Object-Orientated design...") and replace it with "Object-Oriented" to use the
conventional terminology.
In `@daemons/bongbot-ptero.service`:
- Around line 11-16: The systemd service's ExecStart docker run invocation
(ExecStart) doesn't include the --rm flag so stopped containers may be left
behind; update the ExecStart command that runs "docker run --name bongbot-ptero
--env-file ... mirasi/bongbot-ptero:latest" to add the --rm option to the docker
run arguments so the container is automatically removed on exit, and you can
then remove or de-emphasize the existing ExecStopPost cleanup logic that relied
on manual removal.
- Around line 18-20: Systemd unit currently uses Restart=on-failure but no
restart delay, which can cause rapid restart loops; add a RestartSec (e.g., 5s
or suitable backoff) to the unit so systemd waits between attempts—update the
unit file near the Restart= directive to add RestartSec=5s (or another chosen
interval) so the service respects a restart delay after failures; keep the
existing ExecStop and ExecStopPost entries unchanged.
In `@Dockerfile`:
- Around line 25-26: The Dockerfile currently copies empty builder-stage
directories with the lines "COPY --from=builder /app/logs /app/logs" and "COPY
--from=builder /app/data /app/data" even though those paths are later
overmounted by the systemd service; remove those two COPY statements from the
Dockerfile to avoid redundant artifacts and slightly smaller image size, leaving
any required runtime directories to be created by the service or entrypoint
instead of baking empty folders into the release image.
In `@package.json`:
- Around line 12-14: Replace the floating Git ref for the dependency
"bongbot-core" in package.json (the dependency entry "bongbot-core":
"github:Mirasii/BongBot-Core#main") with an immutable ref—either a released tag
(e.g. "#v1.2.3") or a specific commit SHA (e.g. "#<commit-sha>"); after updating
that value, regenerate the lockfile (npm install / yarn install) so CI uses the
exact pinned version and rerun tests to verify the compiled dependency is
stable.
- Around line 8-10: The build-dev NPM script in package.json uses ".." as the
Docker context which requires a parent-directory checkout and sends unrelated
files; update the "build-dev" script and Docker build flow so the Docker context
is the repository root (use "." instead of "..") or change CORE_SOURCE to fetch
Core explicitly during the image build (e.g., a git/http fetch inside the
Dockerfile or a multi-stage build that clones the Core repo) and adjust the
Dockerfile's COPY/ADD references accordingly; target the package.json
"build-dev" script and the Dockerfile instructions that reference CORE_SOURCE,
COPY/ADD paths, or expect ../core so the build no longer depends on the parent
directory.
In `@README.md`:
- Line 10: The phrase "various different commands" in the README should be
simplified to avoid redundancy; update the sentence that currently reads
'BongBot is a Discord Bot made for fun with various different commands.' to use
either "various commands" or "different commands" (e.g., 'BongBot is a Discord
Bot made for fun with various commands.'), leaving the rest of the sentence
unchanged.
In `@src/commands/buildCommands.ts`:
- Line 8: Replace the loose Collection<string, any> with a typed command
interface and use it for client.commands: declare an interface (e.g., Command)
that includes the known structure (data with name, execute method signature
returning Promise<void>, optional fullDesc), then change the Collection type to
Collection<string, Command> where client.commands is assigned so TypeScript
preserves type safety for command objects (refer to client.commands, Collection,
Command, data, execute, fullDesc).
In `@src/commands/pterodactyl/server_status.ts`:
- Around line 56-58: The conditional in setupCollector is written in a
convoluted way; replace the negated equality check so it directly returns when
the subcommand isn't "manage" by using a straightforward inequality check on
interaction.options.getSubcommand() in the async setupCollector method to
improve readability (locate the setupCollector function and the use of
interaction.options.getSubcommand()).
- Around line 270-274: The timestamp passed into buildServerStatusEmbed is
generated with new Date().toLocaleTimeString(), which yields locale-dependent
output; replace this with a locale-stable format (for example use new
Date().toISOString() or format via Intl.DateTimeFormat with an explicit
locale/options) so the embed shows consistent timestamps across environments,
i.e., change the '*Last updated: ' + new Date().toLocaleTimeString() + '*'
construction to a deterministic formatted string before calling
buildServerStatusEmbed.
In `@src/commands/pterodactyl/shared/pterodactylApi.ts`:
- Around line 28-37: sendServerCommand is currently swallowing all errors and
has a small signature formatting issue; update the function to add the missing
space after the comma in the signature (sendServerCommand(caller: Caller,
identifier: string, ...)) and change the catch block to capture the thrown error
(e.g., catch (err)) and log or include the error details before
returning/propagating — use existing helpers like buildHeaders, and ensure
errors from caller.validateServerSSRF and caller.post are surfaced (either by
logging with context via processLogger or rethrowing the error) so callers can
see whether failures are auth, network, or server-state related.
- Around line 10-18: The catch in fetchServerResources silently swallows all
errors; update it to capture the thrown error (e.g., catch (err)) and either log
the error (using an available logger or console.error) with context like
"fetchServerResources failed" before returning null, or selectively
rethrow/propagate specific errors; ensure references to
caller.validateServerSSRF, buildHeaders, and caller.get remain intact so you
only modify the catch block to surface the error details.
In `@src/commands/pterodactyl/shared/serverControlComponents.ts`:
- Around line 36-39: When computing optionsPerMenu using maxRowsForSelects and
allOptions.length, guard against a zero divisor when allOptions.length is 0:
compute the intermediate pages value from Math.min(maxRowsForSelects,
Math.ceil(allOptions.length / 25)), then ensure the divisor is at least 1 (e.g.,
const divisor = Math.max(1, pages)) before doing Math.ceil(allOptions.length /
divisor); update the code that sets optionsPerMenu to use this safe divisor.
This references maxRowsForSelects, optionsPerMenu, and allOptions.
- Around line 66-90: The function disableAllComponents is using any types;
change the parameter and local casts to use discord.js component types to
restore type safety: type the components parameter as
(ActionRowBuilder<StringSelectMenuBuilder> | ActionRowBuilder<ButtonBuilder>)[]
(matching the return type), type firstComponent as StringSelectMenuBuilder |
ButtonBuilder (or AnyComponentBuilder) instead of any, and when iterating use
ButtonBuilder (not APIButtonComponent) so ButtonBuilder.from(...) and
StringSelectMenuBuilder.from(...) keep correct types; also update the forEach
callback signature to expect ButtonBuilder and remove the any casts and explicit
any-actionRow cast while keeping checks against ComponentType to discriminate
branches.
In `@src/commands/pterodactyl/shared/serverStatusEmbed.ts`:
- Around line 31-40: The current block assumes resource.attributes.resources
exists and contains memory_bytes, cpu_absolute, and uptime; to avoid potential
runtime errors in serverStatusEmbed.ts, add a defensive check inside the
existing if (resource && state === 'running') to verify resource.attributes &&
resource.attributes.resources and the specific fields (memory_bytes,
cpu_absolute, uptime) before reading them (or provide sensible fallbacks like
"N/A"); adjust use sites (res, memoryMB, cpuPercent, uptime) so they are only
computed when those fields are present, otherwise skip those lines or insert
safe defaults while keeping calls to formatBytes and formatUptime guarded.
In `@src/commands/pterodactyl/update_server.ts`:
- Around line 27-40: After building the updates object (variable updates) but
before computing finalUrl/finalApiKey and calling fetchServers, short-circuit
when there are no updates: check Object.keys(updates).length === 0 and return or
throw a clear "no changes provided" error (so you don't perform the network
validation). This ensures you only call fetchServers (and compute
finalUrl/finalApiKey) when serverUrl or apiKey were actually changed, and then
proceed to call this.db.updateServer(userId, serverName, updates) as before.
In `@src/helpers/database.ts`:
- Around line 23-35: The table creation in initialize() for pterodactyl_servers
should also create an index on userId to avoid full table scans; update
initialize() (near createTableSQL and this.db.exec) to run a CREATE INDEX IF NOT
EXISTS idx_pterodactyl_servers_userId ON pterodactyl_servers(userId) after the
table is created (or include it in the same exec call) so queries filtering by
userId use the index.
In `@src/services/databasePool.ts`:
- Around line 3-14: The DatabasePool singleton (class DatabasePool with static
instance) never gets cleared which prevents creating fresh pools in tests or new
app lifecycles; add a public static resetInstance() method that calls the
existing closeAll() (or closes and clears connections) and then sets
DatabasePool.instance = undefined/null so subsequent DatabasePool.getInstance()
returns a new instance; ensure resetInstance handles errors from closeAll() and
clears the connections Map as part of the reset.
- Around line 16-23: The getConnection function silently lets
process.env.SERVER_DATABASE override the caller-supplied dbFileName
(resolvedFileName = process.env.SERVER_DATABASE || dbFileName), causing passed
filenames to be ignored; change the logic so an explicitly provided dbFileName
takes precedence over the env var (or document the current behavior).
Concretely, update getConnection to prefer the dbFileName parameter when callers
supply one (use the env var only as a fallback when dbFileName is undefined) and
keep use of this.connections, new Database(resolvedFileName) and the return
this.connections.get(...) logic the same.
In `@tests/commands/buildCommands.test.ts`:
- Around line 35-42: The test's expect(command).toBeDefined() does not narrow
TypeScript types, so accessing command.data.name can be flagged; update the test
after retrieving command from mockClient to assert non-null to the
compiler—e.g., after expect(command).toBeDefined(), create a non-null variable
like const cmd = command! (or use a type guard or cast) and then assert on
cmd.data.name === 'pterodactyl'; reference symbols: buildCommands, mockClient,
command.
In `@tests/commands/pterodactyl/master.test.ts`:
- Around line 263-272: Update the test to assert that the mocked Caller was
constructed with the parsed hosts from PTERODACTYL_ALLOWED_HOSTS: keep setting
process.env.PTERODACTYL_ALLOWED_HOSTS and calling
pterodactylCommand.execute(mockInteraction), then inspect the mocked Caller
constructor (e.g., the mocked Caller symbol) to assert it was called with an
argument containing allowedHosts: ['host1.com','host2.com'] (or equivalent
property used by Caller), and finally still verify mockListExecute was called;
also update the test title to reflect that it verifies construction with parsed
allowed hosts.
In `@tests/commands/pterodactyl/register_server.test.ts`:
- Around line 157-209: Two tests are redundant: the "should handle database
errors" and "should handle errors and call buildError" both throw from
mockAddServer and only assert mockBuildError was invoked; consolidate by
removing one and keeping a single test (e.g., "should handle errors and call
buildError") that covers throwing a generic error from mockAddServer, returning
mockBuildError's error payload, and asserting mockBuildError was called with
(mockInteraction, error) and that the returned result has isError true when
calling registerServerExecute; update or remove the duplicate test block that
references mockAddServer and mockBuildError to avoid overlap.
In `@tests/commands/pterodactyl/remove_server.test.ts`:
- Around line 100-106: The test case "should use the injected database" is
redundant (duplicates the earlier assertion) — delete this duplicate it block in
tests/commands/pterodactyl/remove_server.test.ts, or replace it with a distinct
assertion (e.g., verify interaction reply or error handling) that still invokes
removeServerExecute(mockInteraction) and asserts on mockDeleteServer or other
side effects; locate the block by the it('should use the injected database',
...) wrapper and remove or modify it accordingly.
In `@tests/commands/pterodactyl/server_status.test.ts`:
- Around line 158-163: The test uses mockBuildError.mockResolvedValue but
buildError may be synchronous; confirm the real buildError signature and make
the mock match it: if buildError is synchronous, replace mockResolvedValue with
mockReturnValue (or change the mock to jest.fn<ReturnType, Args>() with the
correct types) so the test doesn't accidentally mask sync/async mismatches;
update uses of mockBuildError and any type annotations to reflect buildError's
actual return type.
In `@tests/commands/pterodactyl/shared/serverControlComponents.test.ts`:
- Line 1: The file contains an unused import of the symbol "jest" (import { jest
} from '@jest/globals') which should be removed; update the test file to
eliminate the unused import so no unused-variable/import warnings remain and
ensure tests still run (remove the "jest" import line or replace it with only
needed imports if any other test helpers are required).
- Around line 78-96: Update the two weak tests for buildServerControlComponents
to assert concrete expected outputs rather than just defined: for the "unknown
server state" case (use buildServerControlComponents with resources state
'starting'), assert the returned rows array length and that the row for the
affected server contains safe/default control values (e.g., disabled or
"unknown" status fields) and that no exceptions occur; for the "null resources"
case, assert that buildServerControlComponents returns a row with the server id
present, uses fallback/default resource values, and that controls that depend on
resources are disabled or marked as unavailable. Reference the
buildServerControlComponents function and the test helpers
createServer/createResources to locate where to check specific properties (row
count, server id, control enabled flags, and status text).
In `@tests/helpers/database.test.ts`:
- Around line 61-65: The afterEach cleanup calls db.close() unconditionally
which can throw if db was never assigned; update the afterEach in
tests/helpers/database.test.ts to defensively check that db is
non-null/undefined (and optionally that typeof db.close === 'function') before
calling db.close(), and await it if it returns a Promise; reference the existing
afterEach, the db variable and the Database constructor to locate and change the
cleanup.
In `@tests/index.test.ts`:
- Around line 288-314: Remove the redundant re-import of discord.js and use the
existing mockClient and its mocked channels/messages directly in this test
(replace the local "Discord" usage and the new Collection with the
mockClient/messages setup), ensure the deleted-message spy remains named
(mockDeleteFn) and passed into the fakeChannel.messages.fetch result, and keep
the handler invocation as-is; also add a brief inline comment explaining the
setTimeout(resolve, 0) flush so future readers understand it’s used to drain the
microtask queue after awaiting handler().
In `@tests/mocks/handlers.ts`:
- Line 18: The mock handler currently sets date: new Date().toISOString(), which
makes the mock value change per run and can cause flaky assertions; change it to
a stable fixed ISO string (e.g., assign a constant like FIXED_ISO_DATE and use
that instead of new Date().toISOString()) so the "date" field in the handler
output is deterministic across test runs and update any tests that rely on the
mocked value to use the same constant.
- Line 1: Remove the leading blank line at the top of the
tests/mocks/handlers.ts file so the file begins immediately with its first
statement (e.g., the first import, export, or handler definition); ensure there
are no extra empty lines before that first token to eliminate the unnecessary
blank line.
In `@tests/mocks/mockDNSValues.ts`:
- Around line 3-14: The mockDNSValues function creates mockResolve4 and
mockResolve6 but doesn't return them, making the jest mocks inaccessible to
tests; update mockDNSValues (the function that calls jest.unstable_mockModule)
to return an object containing mockResolve4 and mockResolve6 so tests can
inspect or override calls/returns, and update any callers to destructure or
capture the returned mocks for assertions or modifications.
In `@tests/services/databasePool.test.ts`:
- Around line 77-93: Move environment variable cleanup out of the test body and
into a safe teardown so failures don't leak state: update tests that set
process.env.SERVER_DATABASE (the two cases calling DatabasePool.getInstance()
and pool.getConnection()) to ensure SERVER_DATABASE is restored/removed in an
afterEach hook or by using try-finally around the test body; reference
DatabasePool.getInstance, DatabasePool.prototype.getConnection (or
pool.getConnection) and MockDatabase when making the change so the env is always
deleted/reset even if the expectation fails.
In `@tests/utils/commandTestUtils.ts`:
- Around line 8-32: The createMockInteraction helper currently performs a
shallow merge so when callers pass options.options it completely replaces
defaults.options and drops stub methods; update createMockInteraction to
deep-merge the nested options object (merge defaults.options with
options.options) so defaults like getInteger, getUser, getSubcommand and
guild/member stubs are preserved while allowing overrides; locate the
createMockInteraction function and change the merge logic to merge
defaults.options and defaults.guild.members.cache as needed with the incoming
options instead of using a single spread that overwrites nested objects.
In `@tsconfig.json`:
- Line 6: The tsconfig.json's "lib" entry unnecessarily includes "DOM" (browser
types) for a Node.js app; edit tsconfig.json and remove "DOM" from the "lib"
array (leave "ES2024" or other Node-appropriate libs) so TypeScript no longer
injects browser globals into the project.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 687bf12f-1bde-4a64-8d95-9291452560c5
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (46)
.env.example.github/dependabot.yml.github/workflows/deploy-develop.yml.github/workflows/deploy.yml.github/workflows/docker-vuln-pr.yml.github/workflows/security-scan.yml.gitignoreCLAUDE.mdDockerfileREADME.mddaemons/bongbot-ptero-develop@.servicedaemons/bongbot-ptero.serviceesbuild.config.mjsjest.config.tspackage.jsonsrc/commands/buildCommands.tssrc/commands/pterodactyl/list_servers.tssrc/commands/pterodactyl/master.tssrc/commands/pterodactyl/register_server.tssrc/commands/pterodactyl/remove_server.tssrc/commands/pterodactyl/server_status.tssrc/commands/pterodactyl/shared/pterodactylApi.tssrc/commands/pterodactyl/shared/serverControlComponents.tssrc/commands/pterodactyl/shared/serverStatusEmbed.tssrc/commands/pterodactyl/update_server.tssrc/helpers/database.tssrc/index.tssrc/services/databasePool.tstests/commands/buildCommands.test.tstests/commands/pterodactyl/list_servers.test.tstests/commands/pterodactyl/master.test.tstests/commands/pterodactyl/register_server.test.tstests/commands/pterodactyl/remove_server.test.tstests/commands/pterodactyl/server_status.test.tstests/commands/pterodactyl/shared/serverControlComponents.test.tstests/commands/pterodactyl/update_server.test.tstests/helpers/database.test.tstests/index.noenv.test.tstests/index.test.tstests/mocks/handlers.tstests/mocks/mockDNSValues.tstests/mocks/server.tstests/services/databasePool.test.tstests/setup.tstests/utils/commandTestUtils.tstsconfig.json
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (6)
CLAUDE.md (2)
111-111: Prefer extending the shared MSW server over creating ad-hoc local serversLine 111 advises creating a local
setupServerfor custom handlers. Given the documented global lifecycle (tests/setup.ts), this can lead to inconsistent test patterns. Preferserver.use(...)on the shared server unless hard isolation is explicitly required.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@CLAUDE.md` at line 111, The guidance should prefer extending the shared MSW server instead of creating ad-hoc local servers: update the text that suggests using setupServer from msw/node to instead instruct developers to call server.use(...) on the shared server (as initialized in tests/setup.ts) for adding custom handlers, and only recommend creating a local setupServer when true hard isolation is required; also mention using jest.useFakeTimers() for time-dependent tests remains valid.
19-19: Tighten wording for clarity in the conventions sectionLine 19 reads a bit awkwardly (“Object-Orientated”, “re-usabilities”). A cleaner phrasing will make the guidance less ambiguous for contributors.
Suggested doc edit
-- Functional components with Object-Orientated design where appropriate for code re-usabilities, e.g. separating database interactions from implementation for re-usability. +- Prefer functional components, with object-oriented design where appropriate, to improve reusability (for example, separating database interactions from business logic).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@CLAUDE.md` at line 19, Replace the awkward sentence "Functional components with Object-Orientated design where appropriate for code re-usabilities, e.g. separating database interactions from implementation for re-usability." with a clearer phrasing such as: "Prefer functional components and apply object-oriented design where appropriate to improve code reuse; for example, separate database interactions from business logic." Update the wording to use "object-oriented" and "code reuse" and keep the example about separating database interactions for clarity.SECURITY.md (2)
9-9: Empty code fence should be removed.Line 9 contains an empty fenced code block with no content and no language specifier. Either populate it or remove it entirely.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@SECURITY.md` at line 9, Remove the empty fenced code block found in SECURITY.md (the triple-backtick block at line 9) — either delete the empty ``` fence entirely or replace it with meaningful content and an optional language specifier; ensure there are no stray empty code fences remaining in the file.
95-95: Minor grammar fix."at time of writing" should be "at the time of writing" for grammatical correctness.
📝 Proposed fix
-- Mirasi's note - at time of writing, the only fix to the high vulnerabilities showing is to revert to version 13 of discord.js, which removes key features used by the commands. +- Mirasi's note - at the time of writing, the only fix to the high vulnerabilities showing is to revert to version 13 of discord.js, which removes key features used by the commands.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@SECURITY.md` at line 95, Update the sentence in Mirasi's note that currently reads "at time of writing" to "at the time of writing" to correct the grammar; locate the line containing "Mirasi's note - at time of writing, the only fix..." and replace that fragment so the sentence reads "Mirasi's note - at the time of writing, the only fix to the high vulnerabilities showing is to revert to version 13 of discord.js, which removes key features used by the commands."ARCHITECTURE.md (1)
100-101: Awkward phrasing."Routes to appropriate subcommand handler" followed immediately by "Subcommand Handler:" creates unnecessary repetition. Consider rephrasing step 3 to "Routes to the appropriate handler" or similar.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ARCHITECTURE.md` around lines 100 - 101, Reword the repetitive phrase in step 3: change "Routes to appropriate subcommand handler" to a clearer, non-redundant form such as "Routes to the appropriate handler" (or "Routes to the chosen subcommand handler") so it doesn't repeat immediately before the "Subcommand Handler:" heading; update the text in the document where that line appears (step 3) accordingly.EXTRAS.md (1)
27-27: Minor grammar issue."includes production and develop daemon unit files" should be "includes production and development daemon unit files" for clarity.
📝 Proposed fix
-- **Systemd service templates**: `daemons/` includes production and develop daemon unit files with restart-on-failure. +- **Systemd service templates**: `daemons/` includes production and development daemon unit files with restart-on-failure.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@EXTRAS.md` at line 27, Summary: the phrase "includes production and develop daemon unit files" is grammatically incorrect; replace "develop" with "development" in the EXTRAS.md sentence. Locate the sentence containing "Systemd service templates: `daemons/` includes production and develop daemon unit files with restart-on-failure" and update it to "includes production and development daemon unit files with restart-on-failure" to fix the grammar while preserving the surrounding formatting and emphasis.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@CLAUDE.md`:
- Line 6: Update the framework name in the CLAUDE.md sentence that currently
reads "NodeJS" to the canonical spelling "Node.js" (edit the phrase in the line
containing "A Discord Bot built on NodeJS in TypeScript..." so it now reads "A
Discord Bot built on Node.js in TypeScript...").
---
Nitpick comments:
In `@ARCHITECTURE.md`:
- Around line 100-101: Reword the repetitive phrase in step 3: change "Routes to
appropriate subcommand handler" to a clearer, non-redundant form such as "Routes
to the appropriate handler" (or "Routes to the chosen subcommand handler") so it
doesn't repeat immediately before the "Subcommand Handler:" heading; update the
text in the document where that line appears (step 3) accordingly.
In `@CLAUDE.md`:
- Line 111: The guidance should prefer extending the shared MSW server instead
of creating ad-hoc local servers: update the text that suggests using
setupServer from msw/node to instead instruct developers to call server.use(...)
on the shared server (as initialized in tests/setup.ts) for adding custom
handlers, and only recommend creating a local setupServer when true hard
isolation is required; also mention using jest.useFakeTimers() for
time-dependent tests remains valid.
- Line 19: Replace the awkward sentence "Functional components with
Object-Orientated design where appropriate for code re-usabilities, e.g.
separating database interactions from implementation for re-usability." with a
clearer phrasing such as: "Prefer functional components and apply
object-oriented design where appropriate to improve code reuse; for example,
separate database interactions from business logic." Update the wording to use
"object-oriented" and "code reuse" and keep the example about separating
database interactions for clarity.
In `@EXTRAS.md`:
- Line 27: Summary: the phrase "includes production and develop daemon unit
files" is grammatically incorrect; replace "develop" with "development" in the
EXTRAS.md sentence. Locate the sentence containing "Systemd service templates:
`daemons/` includes production and develop daemon unit files with
restart-on-failure" and update it to "includes production and development daemon
unit files with restart-on-failure" to fix the grammar while preserving the
surrounding formatting and emphasis.
In `@SECURITY.md`:
- Line 9: Remove the empty fenced code block found in SECURITY.md (the
triple-backtick block at line 9) — either delete the empty ``` fence entirely or
replace it with meaningful content and an optional language specifier; ensure
there are no stray empty code fences remaining in the file.
- Line 95: Update the sentence in Mirasi's note that currently reads "at time of
writing" to "at the time of writing" to correct the grammar; locate the line
containing "Mirasi's note - at time of writing, the only fix..." and replace
that fragment so the sentence reads "Mirasi's note - at the time of writing, the
only fix to the high vulnerabilities showing is to revert to version 13 of
discord.js, which removes key features used by the commands."
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7ba4e438-3ac7-4ac2-a76f-63b8cf6dda30
📒 Files selected for processing (8)
.claude/# Claude Code Prompts.md.gitignoreARCHITECTURE.mdBUGS.mdCLAUDE.mdEXTRAS.mdSECURITY.mdTECHNICAL_DEBT.md
✅ Files skipped from review due to trivial changes (1)
- .gitignore
|
BongBot Development Build 🚀 A new Docker image has been pushed for this pull request.
You can pull the image using: Coverage Report (click to expand)📊 Test Coverage Report📈 Coverage Table
📋 Legend
📊 Overall Summary
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (6)
SECURITY.md (6)
62-67: Provide the audit_logs table schema.You mention an audit_logs table but... there's no schema definition. Add a CREATE TABLE statement so implementers know the exact structure.
Suggested schema
CREATE TABLE IF NOT EXISTS audit_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, action TEXT NOT NULL, server_id TEXT, timestamp INTEGER NOT NULL, details TEXT );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@SECURITY.md` around lines 62 - 67, Add a concrete CREATE TABLE statement for the audit_logs table to SECURITY.md so implementers have the exact schema; include columns matching the referenced fields (audit_logs, id primary key, user_id, action, server_id, timestamp, and optional details), use NOT NULL where appropriate, and include IF NOT EXISTS and a suitable integer/autoincrement primary key to match the suggested schema.
4-4: Minor grammar adjustment....I suppose you should hyphenate "rate-limiting" when it modifies a noun. Not that it really matters for understanding.
Proposed fix
-BongBot-Ptero demonstrates several security strengths (AES-256-GCM encryption, SSRF protection via `Caller`) but has areas of concern in rate limiting and credential handling that could lead to denial of service and data compromise. +BongBot-Ptero demonstrates several security strengths (AES-256-GCM encryption, SSRF protection via `Caller`) but has areas of concern in rate-limiting and credential handling that could lead to denial of service and data compromise.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@SECURITY.md` at line 4, Change the unhyphenated modifier "rate limiting" to "rate-limiting" in the SECURITY.md sentence that currently reads "rate limiting and credential handling" so it correctly hyphenates the compound modifier before the noun; update that phrase to "rate-limiting and credential handling" wherever it appears in the sentence.
90-94: Security checklist could be more comprehensive.Two items is... adequate, I suppose. But you might want to add tests for encryption validation, SSRF protection behaviour, and permission boundaries. Not that it matters much at this scale.
Expanded checklist suggestions
- [ ] Repeated invocations within the cooldown window are rejected - [ ] `npm audit` shows no high/critical dependency vulnerabilities - Mirasi's note - at the time of writing, the only fix to the high vulnerabilities showing is to revert to version 13 of discord.js, which removes key features used by the commands. - [ ] Encryption/decryption roundtrip succeeds for all stored API keys - [ ] SSRF protection rejects requests to non-allowlisted hosts - [ ] Commands properly validate Discord user permissions before execution - [ ] Database operations handle SQLite injection edge cases safely🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@SECURITY.md` around lines 90 - 94, Update the "5. Security Testing Checklist" to include additional specific verification items: add checks for "Encryption/decryption roundtrip succeeds for all stored API keys", "SSRF protection rejects requests to non-allowlisted hosts", "Commands properly validate Discord user permissions before execution", and "Database operations handle SQLite injection edge cases safely"; ensure each new item is a checklist entry and, where applicable, note the expected test/validation (e.g., roundtrip encryption test, allowlist enforcement, permission checks, and injection sanitization) so maintainers can implement corresponding tests or manual verifications.
34-45: Consider adding cleanup for the cooldown map.The proposed cooldown implementation works, but... the Map will grow indefinitely in a long-running bot process. Add periodic cleanup to prevent memory issues.
Enhanced implementation with cleanup
private static cooldowns = new Map<string, number>(); private static cleanupInterval: NodeJS.Timeout | null = null; private static startCleanup(): void { if (this.cleanupInterval) return; this.cleanupInterval = setInterval(() => { const now = Date.now(); const expired = Array.from(this.cooldowns.entries()) .filter(([_, timestamp]) => now - timestamp > 300000); // 5 min expired.forEach(([userId]) => this.cooldowns.delete(userId)); }, 60000); // Check every minute } private checkCooldown(userId: string, durationMs = 5000): boolean { const now = Date.now(); const last = PterodactylMaster.cooldowns.get(userId) ?? 0; if (now - last < durationMs) return false; PterodactylMaster.cooldowns.set(userId, now); return true; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@SECURITY.md` around lines 34 - 45, The cooldown Map PterodactylMaster.cooldowns can grow unbounded; add a static cleanupInterval field and a static startCleanup method that sets a repeating timer (e.g., every 60s) to remove entries older than a chosen TTL (e.g., 5 minutes) by iterating PterodactylMaster.cooldowns and deleting expired userIds, and ensure startCleanup() is invoked (for example at class init or the first time checkCooldown runs) so expired entries are purged and memory usage stays bounded; keep checkCooldown (signature and behavior) but call startCleanup before setting a new timestamp.
94-94: Grammar correction needed."at time of writing" should be "at the time of writing"... if you care about that sort of thing.
Proposed fix
- - Mirasi's note - at time of writing, the only fix to the high vulnerabilities showing is to revert to version 13 of discord.js, which removes key features used by the commands. + - Mirasi's note - at the time of writing, the only fix to the high vulnerabilities showing is to revert to version 13 of discord.js, which removes key features used by the commands.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@SECURITY.md` at line 94, Fix the grammar in SECURITY.md by changing the phrase "at time of writing" to "at the time of writing" in the sentence that begins "Mirasi's note - at time of writing, the only fix..." so the note reads "Mirasi's note - at the time of writing, the only fix..." for correct English usage.
49-59: Add key rotation procedure documentation.The versioned interface is fine... but you should document the actual rotation steps somewhere. Users will need to know how to migrate keys without losing data. If you want, I can help draft that procedure... whatever.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@SECURITY.md` around lines 49 - 59, Document a concrete key rotation procedure for the versioned EncryptedData format (interface EncryptedData { version:number; iv:string; authTag:string; data:string }) and reference the encryption/decryption logic in src/helpers/database.ts (e.g., the encrypt/decrypt or decryptAPIKey/encryptAPIKey functions) so operators can migrate keys safely: describe generating a new key and version, updating configuration (new key identifier/env var), implementing a one-time migration tool (rotateEncryptionKeys) that reads existing records, decrypts with the old key(s) keyed by version, re-encrypts them with the new key/version, verifies integrity, updates the stored records to the new EncryptedData.version, and preserves old keys until migration completes and is verified; include rollback steps, test checklist, and expected downtime/maintenance steps in SECURITY.md or a new KEY_ROTATION.md.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@ARCHITECTURE.md`:
- Line 139: Replace the phrase "Difficult to unit test state polling
independently from Discord interactions" with the idiomatic en-GB wording
"Difficult to unit test state polling independently of Discord interactions" so
the sentence reads correctly; locate the exact line containing that phrase in
ARCHITECTURE.md and update "independently from" to "independently of".
- Around line 96-104: Step 3 ("Master Command Execute") repeats the phrase
"subcommand handler" already used as the heading for Step 4; update Step 3 to
avoid duplicated wording by rephrasing the final bullet to something like
"Routes to the appropriate subcommand" or "Delegates execution to the matching
subcommand handler" so the flow reads: extract name, instantiate Caller, get DB
connection, then route/delegate to the handler; keep Step 4's "Subcommand
Handler" heading and bullets unchanged.
- Around line 9-29: The markdown code fence in ARCHITECTURE.md is untyped which
triggers markdownlint; update the opening fence for the directory tree to
include a language tag (e.g., change "```" to "```text" or "```bash") so the
fenced block is typed, leaving the closing "```" as-is; look for the
directory-tree block near the top of the file (the block containing /src and the
commands/helpers/services entries) and update only the opening backticks.
---
Nitpick comments:
In `@SECURITY.md`:
- Around line 62-67: Add a concrete CREATE TABLE statement for the audit_logs
table to SECURITY.md so implementers have the exact schema; include columns
matching the referenced fields (audit_logs, id primary key, user_id, action,
server_id, timestamp, and optional details), use NOT NULL where appropriate, and
include IF NOT EXISTS and a suitable integer/autoincrement primary key to match
the suggested schema.
- Line 4: Change the unhyphenated modifier "rate limiting" to "rate-limiting" in
the SECURITY.md sentence that currently reads "rate limiting and credential
handling" so it correctly hyphenates the compound modifier before the noun;
update that phrase to "rate-limiting and credential handling" wherever it
appears in the sentence.
- Around line 90-94: Update the "5. Security Testing Checklist" to include
additional specific verification items: add checks for "Encryption/decryption
roundtrip succeeds for all stored API keys", "SSRF protection rejects requests
to non-allowlisted hosts", "Commands properly validate Discord user permissions
before execution", and "Database operations handle SQLite injection edge cases
safely"; ensure each new item is a checklist entry and, where applicable, note
the expected test/validation (e.g., roundtrip encryption test, allowlist
enforcement, permission checks, and injection sanitization) so maintainers can
implement corresponding tests or manual verifications.
- Around line 34-45: The cooldown Map PterodactylMaster.cooldowns can grow
unbounded; add a static cleanupInterval field and a static startCleanup method
that sets a repeating timer (e.g., every 60s) to remove entries older than a
chosen TTL (e.g., 5 minutes) by iterating PterodactylMaster.cooldowns and
deleting expired userIds, and ensure startCleanup() is invoked (for example at
class init or the first time checkCooldown runs) so expired entries are purged
and memory usage stays bounded; keep checkCooldown (signature and behavior) but
call startCleanup before setting a new timestamp.
- Line 94: Fix the grammar in SECURITY.md by changing the phrase "at time of
writing" to "at the time of writing" in the sentence that begins "Mirasi's note
- at time of writing, the only fix..." so the note reads "Mirasi's note - at the
time of writing, the only fix..." for correct English usage.
- Around line 49-59: Document a concrete key rotation procedure for the
versioned EncryptedData format (interface EncryptedData { version:number;
iv:string; authTag:string; data:string }) and reference the
encryption/decryption logic in src/helpers/database.ts (e.g., the
encrypt/decrypt or decryptAPIKey/encryptAPIKey functions) so operators can
migrate keys safely: describe generating a new key and version, updating
configuration (new key identifier/env var), implementing a one-time migration
tool (rotateEncryptionKeys) that reads existing records, decrypts with the old
key(s) keyed by version, re-encrypts them with the new key/version, verifies
integrity, updates the stored records to the new EncryptedData.version, and
preserves old keys until migration completes and is verified; include rollback
steps, test checklist, and expected downtime/maintenance steps in SECURITY.md or
a new KEY_ROTATION.md.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 15277cf2-111f-44fc-b36d-ffd0090abfc0
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (2)
ARCHITECTURE.mdSECURITY.md
|
BongBot Development Build 🚀 A new Docker image has been pushed for this pull request.
You can pull the image using: Coverage Report (click to expand)📊 Test Coverage Report📈 Coverage Table
📋 Legend
📊 Overall Summary
|
There was a problem hiding this comment.
Actionable comments posted: 12
♻️ Duplicate comments (4)
src/commands/pterodactyl/server_status.ts (1)
256-260:⚠️ Potential issue | 🟠 Major...Unhandled async errors in setInterval.
The async callback inside
setIntervalcould throw an error that won't be caught. This leaves the interval running indefinitely ifcheckStatusthrows....This was flagged previously.
🐛 Wrap in try/catch
const pollInterval = setInterval(async () => { - const done = await checkStatus(); - if (done) { clearInterval(pollInterval); } + try { + const done = await checkStatus(); + if (done) { clearInterval(pollInterval); } + } catch (error) { + clearInterval(pollInterval); + this._logger.error(error as Error); + } }, interval);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/pterodactyl/server_status.ts` around lines 256 - 260, The async callback passed to setInterval may reject and leave the interval running; wrap the body of the interval callback in a try/catch around the await checkStatus() call, log or handle the error inside the catch using the existing logger, and ensure you clearInterval(pollInterval) on fatal errors (or when appropriate) so the interval stops; update the callback that references pollInterval and checkStatus to perform this error handling.src/index.ts (2)
42-44:⚠️ Potential issue | 🟠 Major...The interaction state check is still missing.
If
deferReplythrows (e.g., interaction expired), neitherinteraction.deferrednorinteraction.repliedwill be true, yet the catch block unconditionally callsfollowUp. This will throwInteractionNotRepliedError, masking the actual error....I mentioned this before.
🐛 Proposed fix
} catch (error) { - if (interaction.replied) { await interaction.deleteReply(); } - await interaction.followUp(await buildUnknownError(error) as InteractionReplyOptions); + const errorReply = await buildUnknownError(error) as InteractionReplyOptions; + if (interaction.deferred || interaction.replied) { + if (interaction.replied) { await interaction.deleteReply(); } + await interaction.followUp(errorReply); + } else { + await interaction.reply(errorReply); + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/index.ts` around lines 42 - 44, The catch block currently unconditionally calls interaction.followUp which can throw InteractionNotRepliedError when deferReply failed (neither interaction.deferred nor interaction.replied is true); update the error handling in the async interaction handler to first check interaction.deferred || interaction.replied before calling interaction.followUp (leave the existing await interaction.deleteReply() only when interaction.replied is true), and if neither flag is set use interaction.reply with buildUnknownError(error) instead; reference the interaction variable and buildUnknownError function to locate and modify the catch block.
72-73:⚠️ Potential issue | 🟠 Major...The
forEachstill drops promises.The TODO on line 72 acknowledges this, but the issue remains.
message.delete()returns a Promise that isn't awaited. Failures will bypass the surroundingtry/catch, and the new card may be posted before old ones are removed....This was flagged previously.
🐛 Await deletions properly
// TODO: [BUGS 1.2] message.delete() is async but not awaited — use Promise.allSettled with error handling - botMessages?.forEach((message: Message) => message.delete()); + await Promise.allSettled( + botMessages.map((message: Message) => message.delete()) + );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/index.ts` around lines 72 - 73, The current forEach drops the promises from message.delete() so deletions are not awaited; replace the forEach with an awaited Promise.allSettled over botMessages (e.g., await Promise.allSettled(botMessages.map((message: Message) => message.delete()))), then inspect results and log any rejected entries (or handle errors) so failures are surfaced and deletions complete before continuing; ensure you keep the optional chaining guard (botMessages?) and run this inside the surrounding async try/catch context.tests/commands/pterodactyl/server_status.test.ts (1)
101-108:⚠️ Potential issue | 🟠 Major...The queueMicrotask issue remains unaddressed.
Fake timers mock
queueMicrotaskby default, which breaks MSW's fetch implementation since Node.js's global fetch relies on microtasks. Tests may hang or behave unpredictably....This was mentioned before.
🐛 Configure fake timers properly
beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers({ doNotFake: ['queueMicrotask'] }); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/commands/pterodactyl/server_status.test.ts` around lines 101 - 108, The tests enable Jest fake timers in beforeAll which causes queueMicrotask to be mocked and breaks MSW's fetch; fix by capturing the real global.queueMicrotask before calling jest.useFakeTimers (e.g. const realQueueMicrotask = global.queueMicrotask) and then restore it after enabling fake timers (or immediately reassign global.queueMicrotask = realQueueMicrotask) so MSW's fetch continues to work; ensure you revert any changes in afterAll (alongside server.close() and jest.useRealTimers()) to avoid leaking the override.
🧹 Nitpick comments (19)
src/commands/pterodactyl/shared/pterodactyl_api.ts (1)
30-39: ...There's a missing space. How careless.Line 30 is missing a space after the comma between
caller: Caller,identifier. It's a minor formatting inconsistency.✨ Fix formatting
-export async function sendServerCommand(caller: Caller,identifier: string, signal: 'start' | 'stop' | 'restart', serverUrl: string, apiKey: string): Promise<boolean> { +export async function sendServerCommand(caller: Caller, identifier: string, signal: 'start' | 'stop' | 'restart', serverUrl: string, apiKey: string): Promise<boolean> {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/pterodactyl/shared/pterodactyl_api.ts` around lines 30 - 39, In sendServerCommand update the parameter list to add the missing space after the comma between caller: Caller and identifier: string (i.e., change "caller: Caller,identifier: string" to "caller: Caller, identifier: string") to fix the formatting inconsistency in the function signature.src/commands/pterodactyl/shared/server_status_embed.ts (1)
65-73: ...Hmm, there's an edge case you missed.The
formatUptimefunction doesn't handle days. Ifuptimeexceeds 24 hours significantly, the output could show something like "72h 30m" which, whilst technically correct, might be less readable than "3d 0h 30m" for longer uptimes. Not critical, but worth noting.💡 Optional: Add days handling
function formatUptime(milliseconds: number): string { const minutes = Math.floor(milliseconds / 60000); const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + if (days > 0) { + return `${days}d ${hours % 24}h ${minutes % 60}m`; + } if (hours > 0) { return `${hours}h ${minutes % 60}m`; } return `${minutes}m`; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/pterodactyl/shared/server_status_embed.ts` around lines 65 - 73, The formatUptime function currently only computes hours and minutes and can produce unwieldy outputs for multi-day uptimes; update formatUptime to compute days = Math.floor(hours / 24), hours = hours % 24, and minutes as before, then return a formatted string that includes days when days > 0 (e.g. "Xd Yh Zm"), otherwise fall back to "Yh Zm" or "Zm" as appropriate; modify the logic inside formatUptime to use those computed variables so long uptimes are displayed as days/hours/minutes.tests/utils/command_test_utils.ts (1)
34-43: ...It wouldn't hurt to add a type.The
optionsparameter is implicitly typed asany. Consider adding an interface similar toMockInteractionOptionsfor consistency and better autocomplete support. Not critical though.💡 Suggested typing
+interface MockClientOptions { + user?: { displayAvatarURL?: jest.Mock }; + commands?: Map<string, unknown>; +} + -const createMockClient = (options = {}) => { +const createMockClient = (options: MockClientOptions = {}) => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/utils/command_test_utils.ts` around lines 34 - 43, The createMockClient function's options parameter is implicitly any; add a typed interface (e.g., MockClientOptions) mirroring expected shape (user with displayAvatarURL and commands Map) and update the createMockClient signature to accept options: Partial<MockClientOptions> (or MockClientOptions | {}), referencing the existing MockInteractionOptions style for consistency so callers get proper autocomplete and type safety when constructing mock clients.tests/commands/pterodactyl/shared/server_control_components.test.ts (3)
78-96: ...These tests verify existence but not behaviour.The tests for unknown state and null resources only assert that
rowsis defined, but don't verify that no options are generated (which is the expected behaviour for non-actionable states). This could mask bugs where options are incorrectly generated.💡 Suggested stronger assertions
it('should handle unknown server state', () => { const servers = [createServer(1)]; const resources = [createResources('starting')]; const dbServerId = 1; const rows = buildServerControlComponents(servers, resources, dbServerId); - expect(rows).toBeDefined(); + expect(rows).toHaveLength(0); // 'starting' state generates no actions }); it('should handle null resources', () => { const servers = [createServer(1)]; const resources: (ServerResources | null)[] = [null]; const dbServerId = 1; const rows = buildServerControlComponents(servers, resources, dbServerId); - expect(rows).toBeDefined(); + expect(rows).toHaveLength(0); // null resources generate no actions });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/commands/pterodactyl/shared/server_control_components.test.ts` around lines 78 - 96, The tests for unknown server state and null resources currently only assert that rows is defined; update them to assert the expected behavior of buildServerControlComponents by verifying that no actionable options are produced: for the two cases (servers from createServer(1) with resources from createResources('starting') and with resources typed as (ServerResources | null)[] containing null) replace the loose expect(rows).toBeDefined() with assertions that the options list returned by buildServerControlComponents is empty (e.g., expect(rows.options).toHaveLength(0) or the equivalent check for the component structure your function returns) so the tests fail if options are incorrectly generated. Ensure you reference buildServerControlComponents, createServer, createResources and ServerResources when updating the assertions.
43-51: ...This assertion is fragile.The test
should build restart and stop options for running serversexpectsrows.length >= 2but doesn't verify the actual content of the rows. A running server generates 2 options (restart + stop), which fits in one select menu, plus a "Stop All" button row. The assertion passes by coincidence. Consider asserting specific component properties.💡 Suggested stronger assertion
it('should build restart and stop options for running servers', () => { const servers = [createServer(1)]; const resources = [createResources('running')]; const dbServerId = 1; const rows = buildServerControlComponents(servers, resources, dbServerId); - expect(rows.length).toBeGreaterThanOrEqual(2); + expect(rows.length).toBe(2); // 1 select menu + 1 stop-all button + const selectMenu = rows[0].components[0]; + expect(selectMenu.data.options).toHaveLength(2); // restart + stop🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/commands/pterodactyl/shared/server_control_components.test.ts` around lines 43 - 51, The test 'should build restart and stop options for running servers' uses a fragile length check; update it to assert specific component properties returned by buildServerControlComponents(servers, resources, dbServerId): verify there is a row containing an ActionRow with a StringSelect (or select menu) that has exactly two options whose labels or values correspond to "Restart" and "Stop" (or the actual option identifiers used in your code), and verify there is a separate row containing a Button with label "Stop All" (or the exact label/customId), instead of only checking rows.length.
1-1: ...You imported something you didn't use.The
jestimport from@jest/globalsis unused in this file. The tests usedescribe,it, andexpectwhich are available globally in Jest's test environment.🧹 Remove unused import
-import { jest } from '@jest/globals'; +import { describe, it, expect } from '@jest/globals';Or simply remove the import entirely if globals are configured.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/commands/pterodactyl/shared/server_control_components.test.ts` at line 1, The file imports "jest" from '@jest/globals' but never uses it; remove the unused import statement (the line containing "import { jest } from '@jest/globals';") so tests rely on the global Jest helpers (describe, it, expect) or the configured globals.src/commands/pterodactyl/shared/server_control_components.ts (1)
68-92: ...The type safety is lacking, but I understand why.The
any[]parameter andas anyassertions are pragmatic given discord.js's complex generic types. However, you could improve this with a more specific union type to catch obvious misuse at compile time.💡 Suggested typing improvement
+type ComponentRow = ActionRowBuilder<StringSelectMenuBuilder> | ActionRowBuilder<ButtonBuilder>; + export function disableAllComponents( - components: any[] + components: ComponentRow[] ): (ActionRowBuilder<StringSelectMenuBuilder> | ActionRowBuilder<ButtonBuilder>)[] {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/pterodactyl/shared/server_control_components.ts` around lines 68 - 92, The function disableAllComponents uses broad any types; tighten types by replacing the parameter and local casts with a specific union for ActionRowBuilders containing either StringSelectMenuBuilder or ButtonBuilder. Update the signature of disableAllComponents to accept components: (ActionRowBuilder<StringSelectMenuBuilder> | ActionRowBuilder<ButtonBuilder>)[] and remove the "as any" cast for actionRow; type firstComponent as StringSelectMenuBuilder | ButtonBuilder (or the corresponding API component types) and keep the existing branches that check firstComponent.type (ComponentType.StringSelect / ComponentType.Button) while using StringSelectMenuBuilder.from(...) and ButtonBuilder.from(...) with typed inputs so TypeScript catches misuse.src/commands/build_commands.ts (1)
7-11: Consider a stricter type for the commands Collection....Using
Collection<string, any>loses type information. If there's a command interface available inbongbot-coreor locally defined, using it would improve type safety. But... it's not critical.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/build_commands.ts` around lines 7 - 11, The Collection currently uses a loose any type; change client.commands from Collection<string, any> to a strongly-typed Collection using your command interface (e.g., Collection<string, Command>), import the Command type from bongbot-core or the local command interface, and ensure the ExtendedClient.commands property/type matches that Command type; update buildCommands where you iterate commandsArray and set by command.data.name so the compiler preserves the concrete command shape instead of any.tests/services/database_pool.test.ts (1)
77-93: Environment variable cleanup should useafterEach....Currently,
delete process.env.SERVER_DATABASEis inline at the end of each test. If an assertion fails before cleanup, the env var persists and may affect subsequent tests. UsingafterEachwould be more robust.+ afterEach(() => { + delete process.env.SERVER_DATABASE; + }); + it('should use SERVER_DATABASE environment variable if set', () => { process.env.SERVER_DATABASE = 'env-db.db'; const pool = DatabasePool.getInstance(); pool.getConnection(); expect(MockDatabase).toHaveBeenCalledWith('env-db.db'); - delete process.env.SERVER_DATABASE; }); it('should prioritize SERVER_DATABASE env over custom filename', () => { process.env.SERVER_DATABASE = 'env-priority.db'; const pool = DatabasePool.getInstance(); pool.getConnection('ignored.db'); expect(MockDatabase).toHaveBeenCalledWith('env-priority.db'); - delete process.env.SERVER_DATABASE; });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/services/database_pool.test.ts` around lines 77 - 93, Tests set process.env.SERVER_DATABASE inline and delete it at the end of each spec, which can leak if an assertion fails; move this cleanup into an afterEach block so SERVER_DATABASE is always removed regardless of test outcome. Modify the test suite containing DatabasePool.getInstance and pool.getConnection to remove the inline `delete process.env.SERVER_DATABASE` calls from the two specs and add an afterEach that deletes or restores process.env.SERVER_DATABASE (or saves/restores original value) to ensure MockDatabase expectations (e.g., MockDatabase being called with 'env-db.db' or 'env-priority.db') are stable across tests. Ensure the afterEach runs in the same describe scope as the tests referencing SERVER_DATABASE.src/helpers/database.ts (2)
62-68: Silent failure when deleting non-existent server....The
deleteServermethod doesn't verify whether a row was actually deleted. If someone attempts to delete a server that doesn't exist, no error is raised. Whether this is intentional or not is unclear.deleteServer(userId: string, serverName: string): void { const stmt = this.db.prepare(` DELETE FROM pterodactyl_servers WHERE userId = ? AND serverName = ? `); - stmt.run(userId, serverName); + const result = stmt.run(userId, serverName); + if (result.changes === 0) { + throw new Error(`Server "${serverName}" not found for this user.`); + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/helpers/database.ts` around lines 62 - 68, The deleteServer method currently calls stmt.run(userId, serverName) without checking the result, so deletion of a non-existent row silently succeeds; update deleteServer to capture the result of stmt.run(...) and check its changes property (or equivalent return) and either throw a descriptive error or return a boolean indicating success when changes === 0; reference the deleteServer method and the stmt.run call to locate where to add this check and handle the no-op delete accordingly.
84-84: Avoidanytype for the values array....Using
any[]weakens type safety. Consider using a union type instead.-const values: any[] = []; +const values: (string | number)[] = [];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/helpers/database.ts` at line 84, The declaration const values: any[] = [] weakens type safety; replace it with a concrete union that matches your DB column types (for example: const values: (string | number | boolean | null | Date)[] = []), or a narrower union if you know the exact column types, and update any code that pushes into values to conform to that union (e.g., in functions that build queries or bind parameters such as the value-pushing logic surrounding values).src/services/database_pool.ts (1)
16-23: Consider simplifying the API given actual usage patterns....I noticed. The
dbFileNameparameter exists, butprocess.env.SERVER_DATABASEalways takes priority when set. According to the context snippets,master.tsnever passes an argument togetConnection(). This makes the parameter somewhat misleading—callers might expect their argument to be used when it may be silently ignored....If this flexibility is intentional for testing, that's fine. Just... be aware.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/services/database_pool.ts` around lines 16 - 23, The getConnection signature is misleading because getConnection(dbFileName = 'pterodactyl.db') ignores the passed dbFileName whenever process.env.SERVER_DATABASE is set; change the API to be deterministic by removing the dbFileName parameter and always resolving the file name once inside the method (const resolvedFileName = process.env.SERVER_DATABASE || 'pterodactyl.db'), update the method signature getConnection(): Database, and keep the existing connection caching logic (this.connections keyed by resolvedFileName) so callers like master.ts don't pass a silent-ignored argument and the behavior is consistent.src/commands/pterodactyl/master.ts (2)
119-125: ...New DB connection and Caller created per collector setup.The
setupCollectorcreates fresh instances ofDatabasePool.getConnection()andnew Caller(...)rather than reusing those from the originalexecutecall. This is fine sinceDatabasePoolis a singleton that returns cached connections, but creating a newCallerinstance each time is redundant....It's not critical. Just slightly wasteful.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/pterodactyl/master.ts` around lines 119 - 125, The setupCollector callback currently constructs a new Caller and fetches the DB connection each time (via DatabasePool.getInstance().getConnection() and new Caller(getAllowedHosts())) which is redundant; modify the surrounding execute flow so the Caller and DB connection created in execute are captured and reused by setupCollector (pass the existing caller and dbConnection into the ServerStatus constructor inside setupCollector or create the callback with closures that reference the already-created caller and connection), keeping DatabasePool usage as-is but avoiding repeatedly new'ing Caller in setupCollector.
10-13: ...Acceptable, though empty allowlist silently permits nothing.If
PTERODACTYL_ALLOWED_HOSTSis unset, an empty array is returned. Depending on howCallervalidates against an empty allowlist, this could either block all requests or permit all. Worth documenting the expected behaviour.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/pterodactyl/master.ts` around lines 10 - 13, The getAllowedHosts function currently returns [] when PTERODACTYL_ALLOWED_HOSTS is unset, which can cause ambiguous behavior in Caller validation; update getAllowedHosts to make intent explicit by either (1) returning a clear default (e.g., ['*'] to permit all or throw/log and return [] to deny all) or (2) add an inline comment and processLogger.warn describing the chosen policy so callers of getAllowedHosts (and the Caller validation code) have an unambiguous contract; reference getAllowedHosts and the Caller validation usage to implement the chosen explicit default and add a short explanatory log/comment.tests/commands/pterodactyl/server_status.test.ts (1)
51-53: ...Server starts listening at module level, outside lifecycle hooks.
server.listen()on line 53 runs immediately when the test file is parsed, beforebeforeAll. While this works, it's unconventional. Typically,server.listen()is called inbeforeAllto ensure proper setup/teardown ordering.🧹 Move server.listen to beforeAll
const server = setupServer(...handlers); - -server.listen({ onUnhandledRequest: 'bypass' });Then in the test lifecycle:
beforeAll(() => { + server.listen({ onUnhandledRequest: 'bypass' }); jest.useFakeTimers(); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/commands/pterodactyl/server_status.test.ts` around lines 51 - 53, The test file currently calls server.listen() at module top-level after creating server via setupServer(...handlers); remove that module-level server.listen() and instead start the mock server inside a beforeAll hook (call server.listen({ onUnhandledRequest: 'bypass' }) there) and stop it in an afterAll (server.close()); also ensure afterEach resets handlers (server.resetHandlers()) if present so test lifecycle manages setup/teardown correctly for the setupServer instance.src/index.ts (1)
27-27: ...This cast does nothing.The TODO comment acknowledges it, but
interaction as CommandInteractionon its own line without assignment is a no-op. TypeScript already narrows the type afterisCommand(). You may remove this line entirely.🧹 Remove redundant cast
if (!interaction.isCommand()) { return; } - interaction as CommandInteraction; // TODO: [TECHNICAL_DEBT 4] Redundant cast — TypeScript narrows after isCommand() guard🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/index.ts` at line 27, Remove the no-op redundant cast statement "interaction as CommandInteraction" in src/index.ts; after the isCommand() type guard TypeScript already narrows interaction to CommandInteraction, so delete that standalone cast line (it has no assignment or effect) and ensure no other logic depends on that exact statement in the surrounding function handling interactions.src/commands/pterodactyl/server_status.ts (2)
277-281: ...Locale-dependent timestamp formatting.
new Date().toLocaleTimeString()produces different output based on the server's locale settings. For consistency across deployments, consider using a fixed format or UTC.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/pterodactyl/server_status.ts` around lines 277 - 281, The timestamp uses locale-dependent new Date().toLocaleTimeString(), causing inconsistent output; update the call before passing into buildServerStatusEmbed to produce a consistent, timezone-neutral format (e.g., use new Date().toISOString() or format with Intl.DateTimeFormat configured to UTC and a fixed pattern) so all deployments show the same format; locate the embed construction that calls buildServerStatusEmbed(servers, resources, '*Last updated: ' + new Date().toLocaleTimeString() + '*') and replace the toLocaleTimeString() bit with a deterministic UTC/formatted timestamp string.
263-291: ...refreshStatus silently swallows certain failures.If
dbServeris null (line 270-272), the function returns silently without informing the user. The catch block logs but doesn't surface errors. This may be intentional, but it could leave users confused when status doesn't update.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/pterodactyl/server_status.ts` around lines 263 - 291, The refreshStatus method silently returns when db.getServerById(dbServerId) yields null and only logs exceptions in the catch, leaving users uninformed; update refreshStatus to reply (or followUp) via the provided componentInteraction (ButtonInteraction | StringSelectMenuInteraction) when dbServer is not found to inform the user the server record is missing, and in the catch block send a user-facing error reply (or followUp) with a concise message like "Failed to refresh server status" while still logging the full error via this._logger.error(error) so failures are both logged and surfaced to the user instead of being swallowed.tests/index.test.ts (1)
197-227: ...Error handling tests don't verify the state-check fix is needed.The tests on lines 197-227 check
replied=trueandreplied=falsescenarios, but they don't test what happens whendeferReplyitself throws before settingdeferredorreplied. This would exercise the bug identified insrc/index.tslines 42-44....Consider adding a test where
deferReplyrejects to verify the fix once applied.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/index.test.ts` around lines 197 - 227, Add a new test case in tests/index.test.ts that simulates deferReply rejecting (e.g., set interaction.deferReply = jest.fn().mockRejectedValue(new Error('boom'))) to exercise the bug where defer/replied flags aren't set before deferReply throws; call handler(interaction) and assert that mockBuildUnknownError was called and that interaction.deleteReply is NOT called (since no reply was successfully deferred/replied). Reference the existing handler test block and use the same interaction shape (isCommand, commandName, followUp, deleteReply, replied=false) to locate where to insert this test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@EXTRAS.md`:
- Around line 32-57: The section numbering in EXTRAS.md under "2. Missing
Features / UX Friction Points" is non-sequential (headings currently 2.1 "API
Key...", 2.4 "Destructive Operations...", 2.6 "Generic Error Messages"); update
the headings so the subsections are sequential (e.g., 2.1, 2.2, 2.3) or add
brief placeholders noting intentionally removed items, and ensure the visible
headings referenced ("2.1 API Key Submitted in Plaintext Slash Command
Argument", "2.4 Destructive Operations Lack Confirmation", "2.6 Generic Error
Messages") are renumbered consistently throughout the file and any
cross-references are updated accordingly.
In `@README.md`:
- Line 68: Update the README table entry for SERVER_DATABASE to indicate it is
optional (change the ✅ to ❌) and add a short parenthetical note that the default
value 'pterodactyl.db' is applied in database_pool.ts when the env var is not
provided; reference the env var name SERVER_DATABASE and the default assignment
in database_pool.ts so readers can find the implementation easily.
In `@SECURITY.md`:
- Line 6: Replace the misspelled string "Mirasi's note" in SECURITY.md with
"Mirasii's note" to match the PR author's username; locate the exact occurrence
of "Mirasi's note" and update it to "Mirasii's note" (ensure the surrounding
punctuation and capitalization remain unchanged).
- Line 83: Update the sentence in SECURITY.md to use correct grammar and the
correct contributor name: change "at time of writing" to "at the time of
writing" and verify/standardize "Mirasi's" to "Mirasii's" to match the rest of
the document; edit the line containing "Mirasi's note - at time of writing, the
only fix to the high vulnerabilities showing is to revert to version 13 of
discord.js, which removes key features used by the commands." accordingly.
In `@src/commands/pterodactyl/server_status.ts`:
- Around line 114-126: The parseComponentInteraction function currently
destructures the result of splitting componentInteraction.values[0] or
componentInteraction.customId without checking that the split returns at least
three parts; update parseComponentInteraction to first retrieve the raw string
(use componentInteraction.values[0] for StringSelectMenu and
componentInteraction.customId for buttons), call .split(':'), validate that
parts.length >= 3, and only then assign dbServerId, identifier, action; if
validation fails, either throw a clear Error or return a safe fallback (e.g.,
null or an object with empty strings) and ensure callers handle that case.
Optionally extract this logic into a small ComponentIdParser helper used by
parseComponentInteraction to centralize validation and parsing.
- Around line 159-168: The loop over results is unsafely casting each `result`
to `PromiseFulfilledResult` and accessing `value` directly; instead check
`result.status === 'fulfilled'` before reading `result.value`, push
`result.value.identifier` into `successfulIdentifiers` and handle the rejected
branch by logging `result.reason` (via `this._logger.debug`/error) and adding an
appropriate name/identifier to `failedServers` (or derive it from the rejected
info), so replace the cast in the loop that references `results`,
`failedServers`, `successfulIdentifiers`, and `this._logger` with a proper type
guard that handles both fulfilled and rejected cases safely.
In `@src/commands/pterodactyl/shared/server_control_components.ts`:
- Around line 37-52: The current optionsPerMenu calculation can exceed Discord's
25-option limit; change the logic in the block that computes optionsPerMenu (and
the subsequent for-loop that slices allOptions and builds
StringSelectMenuBuilder rows) to cap optionsPerMenu at 25 and compute the number
of menus first (using Math.ceil(allOptions.length / optionsPerMenu)) so each
menu has at most 25 options; also use a menu index (menuIdx = 0..n-1) for the
customId (`server_control:${dbServerId}:menu${menuIdx}`) when creating each
StringSelectMenuBuilder to avoid embedding the slice step value in the id.
In `@src/helpers/database.ts`:
- Line 141: The code creates a key with Buffer.from(process.env.ENCRYPTION_KEY!,
'hex') without validating the ENCRYPTION_KEY value; add startup validation that
ENCRYPTION_KEY exists, is valid hex, and is exactly 64 hex characters (32 bytes)
before creating the Buffer (e.g., validate in app startup and/or before the
const key assignment), and if invalid throw a clear, descriptive error so
Buffer.from is never called with a malformed value.
In `@src/index.ts`:
- Around line 86-88: The console logs run before login completes because
bot.login(token) is not awaited; change the startup to await the Promise
returned by bot.login(token) inside an async context and wrap it in a try/catch
so failures are logged and the process exits (or handles retry) instead of
continuing; move the two console.log calls ("BongBot Online!" and the sessionId
log referencing process.env.SESSION_ID) to after a successful await of
bot.login(token), and in the catch call processLogger.error or console.error
with the caught error and fail fast (e.g., process.exit(1)).
In `@TECHNICAL_DEBT.md`:
- Around line 7-43: The section numbering in TECHNICAL_DEBT.md is
inconsistent—there is "### 1.1 Unsafe `as any` Cast for Collector Detection"
followed by "### 1.3 Fire-and-Forget Message Edit in Collector Cleanup" with no
1.2; update the headings to be sequential (e.g., rename "### 1.3 Fire-and-Forget
Message Edit in Collector Cleanup" to "### 1.2 ..." or insert the missing 1.2
section if content was omitted) so the enumerations are contiguous and
consistent across the document; ensure any internal references to "1.3" are also
updated to the new number.
In `@tests/commands/pterodactyl/shared/server_control_components.test.ts`:
- Line 7: The test import references a non-existent module name; update the
import statement that brings in PterodactylServer and ServerResources to point
to the renamed module (snake_case) instead of "pterodactylApi.js"—replace it
with the correct module name "pterodactyl_api" (and appropriate extension or
rely on TS resolution) so the import of PterodactylServer and ServerResources
resolves.
In `@tests/utils/command_test_utils.ts`:
- Around line 28-32: The current shallow merge ({ ...defaults, ...options })
overwrites nested defaults.options when callers pass an options.options object;
instead perform a shallow merge for the top-level object but also merge the
nested options property so default getters (getInteger, getUser, getSubcommand,
etc.) are preserved. Update the return to merge defaults and options, and set
the returned options property to a merged object (e.g., merged = { ...defaults,
...options, options: { ...defaults.options, ...(options?.options || {}) } }) so
callers can override specific getters without losing others; reference the local
variables defaults and options and the nested property options to locate where
to change.
---
Duplicate comments:
In `@src/commands/pterodactyl/server_status.ts`:
- Around line 256-260: The async callback passed to setInterval may reject and
leave the interval running; wrap the body of the interval callback in a
try/catch around the await checkStatus() call, log or handle the error inside
the catch using the existing logger, and ensure you clearInterval(pollInterval)
on fatal errors (or when appropriate) so the interval stops; update the callback
that references pollInterval and checkStatus to perform this error handling.
In `@src/index.ts`:
- Around line 42-44: The catch block currently unconditionally calls
interaction.followUp which can throw InteractionNotRepliedError when deferReply
failed (neither interaction.deferred nor interaction.replied is true); update
the error handling in the async interaction handler to first check
interaction.deferred || interaction.replied before calling interaction.followUp
(leave the existing await interaction.deleteReply() only when
interaction.replied is true), and if neither flag is set use interaction.reply
with buildUnknownError(error) instead; reference the interaction variable and
buildUnknownError function to locate and modify the catch block.
- Around line 72-73: The current forEach drops the promises from
message.delete() so deletions are not awaited; replace the forEach with an
awaited Promise.allSettled over botMessages (e.g., await
Promise.allSettled(botMessages.map((message: Message) => message.delete()))),
then inspect results and log any rejected entries (or handle errors) so failures
are surfaced and deletions complete before continuing; ensure you keep the
optional chaining guard (botMessages?) and run this inside the surrounding async
try/catch context.
In `@tests/commands/pterodactyl/server_status.test.ts`:
- Around line 101-108: The tests enable Jest fake timers in beforeAll which
causes queueMicrotask to be mocked and breaks MSW's fetch; fix by capturing the
real global.queueMicrotask before calling jest.useFakeTimers (e.g. const
realQueueMicrotask = global.queueMicrotask) and then restore it after enabling
fake timers (or immediately reassign global.queueMicrotask = realQueueMicrotask)
so MSW's fetch continues to work; ensure you revert any changes in afterAll
(alongside server.close() and jest.useRealTimers()) to avoid leaking the
override.
---
Nitpick comments:
In `@src/commands/build_commands.ts`:
- Around line 7-11: The Collection currently uses a loose any type; change
client.commands from Collection<string, any> to a strongly-typed Collection
using your command interface (e.g., Collection<string, Command>), import the
Command type from bongbot-core or the local command interface, and ensure the
ExtendedClient.commands property/type matches that Command type; update
buildCommands where you iterate commandsArray and set by command.data.name so
the compiler preserves the concrete command shape instead of any.
In `@src/commands/pterodactyl/master.ts`:
- Around line 119-125: The setupCollector callback currently constructs a new
Caller and fetches the DB connection each time (via
DatabasePool.getInstance().getConnection() and new Caller(getAllowedHosts()))
which is redundant; modify the surrounding execute flow so the Caller and DB
connection created in execute are captured and reused by setupCollector (pass
the existing caller and dbConnection into the ServerStatus constructor inside
setupCollector or create the callback with closures that reference the
already-created caller and connection), keeping DatabasePool usage as-is but
avoiding repeatedly new'ing Caller in setupCollector.
- Around line 10-13: The getAllowedHosts function currently returns [] when
PTERODACTYL_ALLOWED_HOSTS is unset, which can cause ambiguous behavior in Caller
validation; update getAllowedHosts to make intent explicit by either (1)
returning a clear default (e.g., ['*'] to permit all or throw/log and return []
to deny all) or (2) add an inline comment and processLogger.warn describing the
chosen policy so callers of getAllowedHosts (and the Caller validation code)
have an unambiguous contract; reference getAllowedHosts and the Caller
validation usage to implement the chosen explicit default and add a short
explanatory log/comment.
In `@src/commands/pterodactyl/server_status.ts`:
- Around line 277-281: The timestamp uses locale-dependent new
Date().toLocaleTimeString(), causing inconsistent output; update the call before
passing into buildServerStatusEmbed to produce a consistent, timezone-neutral
format (e.g., use new Date().toISOString() or format with Intl.DateTimeFormat
configured to UTC and a fixed pattern) so all deployments show the same format;
locate the embed construction that calls buildServerStatusEmbed(servers,
resources, '*Last updated: ' + new Date().toLocaleTimeString() + '*') and
replace the toLocaleTimeString() bit with a deterministic UTC/formatted
timestamp string.
- Around line 263-291: The refreshStatus method silently returns when
db.getServerById(dbServerId) yields null and only logs exceptions in the catch,
leaving users uninformed; update refreshStatus to reply (or followUp) via the
provided componentInteraction (ButtonInteraction | StringSelectMenuInteraction)
when dbServer is not found to inform the user the server record is missing, and
in the catch block send a user-facing error reply (or followUp) with a concise
message like "Failed to refresh server status" while still logging the full
error via this._logger.error(error) so failures are both logged and surfaced to
the user instead of being swallowed.
In `@src/commands/pterodactyl/shared/pterodactyl_api.ts`:
- Around line 30-39: In sendServerCommand update the parameter list to add the
missing space after the comma between caller: Caller and identifier: string
(i.e., change "caller: Caller,identifier: string" to "caller: Caller,
identifier: string") to fix the formatting inconsistency in the function
signature.
In `@src/commands/pterodactyl/shared/server_control_components.ts`:
- Around line 68-92: The function disableAllComponents uses broad any types;
tighten types by replacing the parameter and local casts with a specific union
for ActionRowBuilders containing either StringSelectMenuBuilder or
ButtonBuilder. Update the signature of disableAllComponents to accept
components: (ActionRowBuilder<StringSelectMenuBuilder> |
ActionRowBuilder<ButtonBuilder>)[] and remove the "as any" cast for actionRow;
type firstComponent as StringSelectMenuBuilder | ButtonBuilder (or the
corresponding API component types) and keep the existing branches that check
firstComponent.type (ComponentType.StringSelect / ComponentType.Button) while
using StringSelectMenuBuilder.from(...) and ButtonBuilder.from(...) with typed
inputs so TypeScript catches misuse.
In `@src/commands/pterodactyl/shared/server_status_embed.ts`:
- Around line 65-73: The formatUptime function currently only computes hours and
minutes and can produce unwieldy outputs for multi-day uptimes; update
formatUptime to compute days = Math.floor(hours / 24), hours = hours % 24, and
minutes as before, then return a formatted string that includes days when days >
0 (e.g. "Xd Yh Zm"), otherwise fall back to "Yh Zm" or "Zm" as appropriate;
modify the logic inside formatUptime to use those computed variables so long
uptimes are displayed as days/hours/minutes.
In `@src/helpers/database.ts`:
- Around line 62-68: The deleteServer method currently calls stmt.run(userId,
serverName) without checking the result, so deletion of a non-existent row
silently succeeds; update deleteServer to capture the result of stmt.run(...)
and check its changes property (or equivalent return) and either throw a
descriptive error or return a boolean indicating success when changes === 0;
reference the deleteServer method and the stmt.run call to locate where to add
this check and handle the no-op delete accordingly.
- Line 84: The declaration const values: any[] = [] weakens type safety; replace
it with a concrete union that matches your DB column types (for example: const
values: (string | number | boolean | null | Date)[] = []), or a narrower union
if you know the exact column types, and update any code that pushes into values
to conform to that union (e.g., in functions that build queries or bind
parameters such as the value-pushing logic surrounding values).
In `@src/index.ts`:
- Line 27: Remove the no-op redundant cast statement "interaction as
CommandInteraction" in src/index.ts; after the isCommand() type guard TypeScript
already narrows interaction to CommandInteraction, so delete that standalone
cast line (it has no assignment or effect) and ensure no other logic depends on
that exact statement in the surrounding function handling interactions.
In `@src/services/database_pool.ts`:
- Around line 16-23: The getConnection signature is misleading because
getConnection(dbFileName = 'pterodactyl.db') ignores the passed dbFileName
whenever process.env.SERVER_DATABASE is set; change the API to be deterministic
by removing the dbFileName parameter and always resolving the file name once
inside the method (const resolvedFileName = process.env.SERVER_DATABASE ||
'pterodactyl.db'), update the method signature getConnection(): Database, and
keep the existing connection caching logic (this.connections keyed by
resolvedFileName) so callers like master.ts don't pass a silent-ignored argument
and the behavior is consistent.
In `@tests/commands/pterodactyl/server_status.test.ts`:
- Around line 51-53: The test file currently calls server.listen() at module
top-level after creating server via setupServer(...handlers); remove that
module-level server.listen() and instead start the mock server inside a
beforeAll hook (call server.listen({ onUnhandledRequest: 'bypass' }) there) and
stop it in an afterAll (server.close()); also ensure afterEach resets handlers
(server.resetHandlers()) if present so test lifecycle manages setup/teardown
correctly for the setupServer instance.
In `@tests/commands/pterodactyl/shared/server_control_components.test.ts`:
- Around line 78-96: The tests for unknown server state and null resources
currently only assert that rows is defined; update them to assert the expected
behavior of buildServerControlComponents by verifying that no actionable options
are produced: for the two cases (servers from createServer(1) with resources
from createResources('starting') and with resources typed as (ServerResources |
null)[] containing null) replace the loose expect(rows).toBeDefined() with
assertions that the options list returned by buildServerControlComponents is
empty (e.g., expect(rows.options).toHaveLength(0) or the equivalent check for
the component structure your function returns) so the tests fail if options are
incorrectly generated. Ensure you reference buildServerControlComponents,
createServer, createResources and ServerResources when updating the assertions.
- Around line 43-51: The test 'should build restart and stop options for running
servers' uses a fragile length check; update it to assert specific component
properties returned by buildServerControlComponents(servers, resources,
dbServerId): verify there is a row containing an ActionRow with a StringSelect
(or select menu) that has exactly two options whose labels or values correspond
to "Restart" and "Stop" (or the actual option identifiers used in your code),
and verify there is a separate row containing a Button with label "Stop All" (or
the exact label/customId), instead of only checking rows.length.
- Line 1: The file imports "jest" from '@jest/globals' but never uses it; remove
the unused import statement (the line containing "import { jest } from
'@jest/globals';") so tests rely on the global Jest helpers (describe, it,
expect) or the configured globals.
In `@tests/index.test.ts`:
- Around line 197-227: Add a new test case in tests/index.test.ts that simulates
deferReply rejecting (e.g., set interaction.deferReply =
jest.fn().mockRejectedValue(new Error('boom'))) to exercise the bug where
defer/replied flags aren't set before deferReply throws; call
handler(interaction) and assert that mockBuildUnknownError was called and that
interaction.deleteReply is NOT called (since no reply was successfully
deferred/replied). Reference the existing handler test block and use the same
interaction shape (isCommand, commandName, followUp, deleteReply, replied=false)
to locate where to insert this test.
In `@tests/services/database_pool.test.ts`:
- Around line 77-93: Tests set process.env.SERVER_DATABASE inline and delete it
at the end of each spec, which can leak if an assertion fails; move this cleanup
into an afterEach block so SERVER_DATABASE is always removed regardless of test
outcome. Modify the test suite containing DatabasePool.getInstance and
pool.getConnection to remove the inline `delete process.env.SERVER_DATABASE`
calls from the two specs and add an afterEach that deletes or restores
process.env.SERVER_DATABASE (or saves/restores original value) to ensure
MockDatabase expectations (e.g., MockDatabase being called with 'env-db.db' or
'env-priority.db') are stable across tests. Ensure the afterEach runs in the
same describe scope as the tests referencing SERVER_DATABASE.
In `@tests/utils/command_test_utils.ts`:
- Around line 34-43: The createMockClient function's options parameter is
implicitly any; add a typed interface (e.g., MockClientOptions) mirroring
expected shape (user with displayAvatarURL and commands Map) and update the
createMockClient signature to accept options: Partial<MockClientOptions> (or
MockClientOptions | {}), referencing the existing MockInteractionOptions style
for consistency so callers get proper autocomplete and type safety when
constructing mock clients.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 2c35e6f6-3efe-4b78-b333-efbe01213cfc
📒 Files selected for processing (31)
CLAUDE.mdEXTRAS.mdREADME.mdSECURITY.mdTECHNICAL_DEBT.mdsrc/commands/build_commands.tssrc/commands/pterodactyl/list_servers.tssrc/commands/pterodactyl/master.tssrc/commands/pterodactyl/register_server.tssrc/commands/pterodactyl/remove_server.tssrc/commands/pterodactyl/server_status.tssrc/commands/pterodactyl/shared/pterodactyl_api.tssrc/commands/pterodactyl/shared/server_control_components.tssrc/commands/pterodactyl/shared/server_status_embed.tssrc/commands/pterodactyl/update_server.tssrc/helpers/database.tssrc/index.tssrc/services/database_pool.tstests/commands/build_commands.test.tstests/commands/pterodactyl/list_servers.test.tstests/commands/pterodactyl/master.test.tstests/commands/pterodactyl/register_server.test.tstests/commands/pterodactyl/remove_server.test.tstests/commands/pterodactyl/server_status.test.tstests/commands/pterodactyl/shared/server_control_components.test.tstests/commands/pterodactyl/update_server.test.tstests/index.noenv.test.tstests/index.test.tstests/mocks/mock_dns_values.tstests/services/database_pool.test.tstests/utils/command_test_utils.ts
✅ Files skipped from review due to trivial changes (4)
- tests/index.noenv.test.ts
- tests/commands/pterodactyl/list_servers.test.ts
- tests/commands/pterodactyl/update_server.test.ts
- tests/commands/pterodactyl/register_server.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
- src/commands/pterodactyl/remove_server.ts
- src/commands/pterodactyl/register_server.ts
- tests/commands/pterodactyl/remove_server.test.ts
- src/commands/pterodactyl/list_servers.ts
- src/commands/pterodactyl/update_server.ts
- tests/commands/pterodactyl/master.test.ts
Initial version of MicroBot
Summary by CodeRabbit
Release Notes
New Features
/pterodactylDiscord command with server management subcommands: register, list, manage status, update credentials, and remove servers.Deployment