Skip to content

Add /warn command#135

Open
itasimo wants to merge 2 commits into
mainfrom
itasimo/warning
Open

Add /warn command#135
itasimo wants to merge 2 commits into
mainfrom
itasimo/warning

Conversation

@itasimo

@itasimo itasimo commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator

Closes #82

itasimo added 2 commits June 25, 2026 23:15
Add /warn to warn a user, /unwarn to remove a warn given the warnId and /warns to get the warn history of a user
Add docker-compose.yml to run the bot with a local database
Add cross-env to package.json to set environment variables in a cross-platform way
Refactor src/modules/moderation/index.ts to help with readability
Features:
- Finished implementing the `/warn` command with automatic warning-based moderation.
- Added the `tgnumber` argument type to enforce strict numeric inputs instead of `numberOrString`.
- Added the `ephemeralInGroup` function to send temporary messages in groups while keeping them permanent in private chats.

Fixes:
- Fixed a bug where `/ban_all` crashed if the bot lacked access to the logging group. Decoupled the logic so that if the initial message fails, progress updates are skipped but command execution continues normally.
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds a warning system with /warn, /unwarn, and /warns commands, including auto-kick at group thresholds and auto-ban-all at global thresholds. Introduces a resolveUser utility and tgnumber Zod schema. Makes ban-all Telegram progress tracking optional when the logger is unreachable. Adds docker-compose.yml for Redis and InfluxDB services and updates package.json for cross-env and a local backend reference.

Changes

Warning System & Ban-All Resilience

Layer / File(s) Summary
Shared utilities: tgnumber and resolveUser
src/utils/types.ts, src/utils/users.ts
Adds tgnumber Zod schema validating numeric strings and resolveUser that fetches a User with ephemeral error replies; refactors getOverloadUser to delegate to resolveUser.
Warning type and /warn, /unwarn, /warns commands
src/commands/moderation/warn.ts, src/commands/moderation/index.ts
Defines Warning type and implements three command handlers: /warn creates a warning and triggers auto-kick or auto-ban-all at thresholds; /unwarn deletes by ID; /warns lists history. Registers the collection in the moderation index.
Optional messageId in ban-all progress tracking
src/modules/moderation/ban-all.ts, src/modules/tg-logger/index.ts
Changes BanAllFlow.messageId to optional, guards progress updates when absent, makes banAllProgress a no-op without messageId, adds WARN/UNWARN/WARNS to MOD_ACTION_TITLE.
ModerationClass refactor and kickAction helper
src/modules/moderation/index.ts
Reorders class definition, extracts a kickAction helper for the KICK case in perform(), and expands JSDoc throughout without changing pipeline behavior.

Infrastructure & Build Config

Layer / File(s) Summary
Docker Compose services
docker-compose.yml
Adds redis (7-alpine, password-protected) and influxdb (2.7-alpine, init via env vars) services with named volumes.
package.json updates
package.json
Prefixes start/dev:raw scripts with cross-env, adds cross-env devDependency, and switches @polinetwork/backend to file:../backend/package.

Possibly related PRs

  • PoliNetworkOrg/telegram#98: Modifies BanAllQueue.initiateBanAll and ban-all flow handling, directly overlapping with the optional messageId changes in this PR.
  • PoliNetworkOrg/telegram#112: Adjusts ban-all progress reporting in executor/progress handlers, the same area made conditional on messageId presence here.
🚥 Pre-merge checks | ✅ 1 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning docker-compose.yml, cross-env script changes, and the local backend dependency are support changes not required by #82. Split unrelated infra/dev changes into a separate PR or justify them in the linked issue if they are required.
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive Warn commands and automated penalties are present, but the warning storage/schema requirement can't be verified from the provided summaries. Provide the warning persistence/schema changes or the relevant backend files so compliance can be verified.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and matches the main change: adding the /warn command for moderation.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docker-compose.yml`:
- Around line 8-10: The Redis service configuration is hardcoding the password
and exposing the port broadly; update the docker-compose Redis service to use an
environment-substituted secret that matches the REDIS_PASSWORD used by
src/redis/index.ts, and bind the published port to localhost for development.
Adjust the Redis service command and ports mapping so credentials are not
committed and the app can still authenticate consistently.
- Around line 18-26: The InfluxDB service configuration currently hardcodes
admin credentials in the compose setup and exposes the port broadly, so update
the docker-compose service to read sensitive values from environment variables
or an env file instead of embedding them directly. Use the existing InfluxDB
init settings in the service definition to map `DOCKER_INFLUXDB_INIT_*` values
from env-driven inputs, and ensure the app’s `INFLUXDB_TOKEN` stays the single
source of truth for telemetry auth to avoid config drift. Identify the changes
in the InfluxDB service block by its `DOCKER_INFLUXDB_INIT_*` environment
entries and `ports` section.

In `@package.json`:
- Line 9: The start script currently depends on cross-env, but it may not be
available in production installs. Update the package.json scripts or dependency
list so start no longer relies on a dev-only binary: either move cross-env into
dependencies or remove it from the start command and use a plain
NODE_ENV=production launch. Make the change in the start script entry so
production startup works after npm ci --omit=dev.
- Line 42: The package manifest currently depends on a sibling-local backend
path, which breaks installs when that neighboring directory is absent. Update
the dependency for `@polinetwork/backend` in package.json to use a workspace
reference or a published semver version instead of the file:../backend/package
path so fresh CI and release installs can resolve it reliably.

In `@src/commands/moderation/warn.ts`:
- Around line 126-132: The auto-ban path in warn.ts is bypassing the same
ban-all protection used by the manual /ban_all flow. Update the
warning-threshold branch in the warn command to reuse the existing ban-all
bypass guard before calling tgLogger.banAll, so protected/privileged accounts
cannot be auto-banned across all groups. Locate the logic around the
getTotalActiveCount query and the tgLogger.banAll call and apply the same
permission check used by the ban-all command path.
- Around line 109-132: The auto-moderation checks in warn.ts currently hardcode
`>= 3` and `>= 4`, which can retrigger `Moderation.kick` and
`modules.get("tgLogger").banAll` on every later warning instead of only when the
threshold is first crossed. Update the warning flow to compare against
configurable threshold values (read from config rather than literals) and gate
the actions so they fire only once when moving from below the threshold to
at/above it, using the existing `getActiveCountInGroup` and
`getTotalActiveCount` checks as the decision points.
- Around line 168-171: The /unwarn flow in warn.ts only deletes by warnId, which
allows cross-group deletion without chat context. Update the moderation command
handler around the deleteById mutate call to also pass the current group/chat
identifier, and make sure the backend authorization path in the deleteById
mutation verifies the warning belongs to that chat before deleting. Use the
existing warnId handling in this command plus the current chat/group context
available in the moderation command to scope the request.
- Around line 68-75: The /warn command currently allows DM execution via scope:
"both", but the handler always persists groupId from context.chat.id, so
warnings in private chats get tied to the DM instead of a real group. Update the
moderation/warn.ts command definition and handler so warn only runs in group
chats or requires an explicit target group before calling the warn mutation,
using the existing handler, context, and repliedTo flow to enforce a valid
group-scoped groupId.

In `@src/utils/types.ts`:
- Around line 42-47: The tgnumber schema currently accepts non-integer and
non-decimal-edge values like negatives, floats, hex strings, and Infinity, which
is too loose for warning IDs. Tighten the refinement in tgnumber so it only
accepts valid finite positive integer strings (or whatever exact ID shape the
/unwarn caller expects) before transforming, and keep the rejection at parse
time instead of allowing malformed values through to deleteById.

In `@src/utils/users.ts`:
- Around line 48-50: The lookup failure path in resolveUser’s caller currently
returns Err("SILENT_ERROR"), which causes a second generic reply after
resolveUser() already handles the “user not found” case. Update the logic around
the user resolution check to stop returning an error there and instead exit
early with no additional response, so commands like ban, mute, and warn only
send the single intended failure message.
🪄 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: 462fedc1-d6a4-417d-9db6-369936c2e5ec

📥 Commits

Reviewing files that changed from the base of the PR and between 56dd41a and 5fa985d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • docker-compose.yml
  • package.json
  • src/commands/moderation/index.ts
  • src/commands/moderation/warn.ts
  • src/modules/moderation/ban-all.ts
  • src/modules/moderation/index.ts
  • src/modules/tg-logger/index.ts
  • src/utils/types.ts
  • src/utils/users.ts

Comment thread docker-compose.yml
Comment on lines +8 to +10
command: redis-server --requirepass "Radmin"
ports:
- "6379:6379"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Avoid hardcoded Redis credentials on a host-exposed port.

Radmin is committed and Redis is published on all interfaces; it also has to match REDIS_PASSWORD from src/redis/index.ts, or the app cannot authenticate. Use an env-substituted secret and bind locally for dev.

Suggested change
-    command: redis-server --requirepass "Radmin"
+    command: ["redis-server", "--requirepass", "${REDIS_PASSWORD:?REDIS_PASSWORD must be set}"]
     ports:
-      - "6379:6379"
+      - "127.0.0.1:6379:6379"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
command: redis-server --requirepass "Radmin"
ports:
- "6379:6379"
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD:?REDIS_PASSWORD must be set}"]
ports:
- "127.0.0.1:6379:6379"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker-compose.yml` around lines 8 - 10, The Redis service configuration is
hardcoding the password and exposing the port broadly; update the docker-compose
Redis service to use an environment-substituted secret that matches the
REDIS_PASSWORD used by src/redis/index.ts, and bind the published port to
localhost for development. Adjust the Redis service command and ports mapping so
credentials are not committed and the app can still authenticate consistently.

Comment thread docker-compose.yml
Comment on lines +18 to +26
ports:
- "8086:8086"
environment:
- DOCKER_INFLUXDB_INIT_MODE=setup
- DOCKER_INFLUXDB_INIT_USERNAME=Iadmin
- DOCKER_INFLUXDB_INIT_PASSWORD=Iadmin1234567
- DOCKER_INFLUXDB_INIT_ORG=polinetwork
- DOCKER_INFLUXDB_INIT_BUCKET=telegram_bot
- DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=tu_token_secreto_de_32_caracteres_o_mas

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Move InfluxDB admin credentials out of the compose file.

The admin password/token are committed and the service is exposed on all interfaces. Since telemetry depends on INFLUXDB_TOKEN, make these env-driven so local setup and app config cannot drift.

Suggested change
     ports:
-      - "8086:8086"
+      - "127.0.0.1:8086:8086"
     environment:
       - DOCKER_INFLUXDB_INIT_MODE=setup
-      - DOCKER_INFLUXDB_INIT_USERNAME=Iadmin
-      - DOCKER_INFLUXDB_INIT_PASSWORD=Iadmin1234567
+      - DOCKER_INFLUXDB_INIT_USERNAME=${INFLUXDB_INIT_USERNAME:?INFLUXDB_INIT_USERNAME must be set}
+      - DOCKER_INFLUXDB_INIT_PASSWORD=${INFLUXDB_INIT_PASSWORD:?INFLUXDB_INIT_PASSWORD must be set}
       - DOCKER_INFLUXDB_INIT_ORG=polinetwork
       - DOCKER_INFLUXDB_INIT_BUCKET=telegram_bot
-      - DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=tu_token_secreto_de_32_caracteres_o_mas
+      - DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=${INFLUXDB_TOKEN:?INFLUXDB_TOKEN must be set}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ports:
- "8086:8086"
environment:
- DOCKER_INFLUXDB_INIT_MODE=setup
- DOCKER_INFLUXDB_INIT_USERNAME=Iadmin
- DOCKER_INFLUXDB_INIT_PASSWORD=Iadmin1234567
- DOCKER_INFLUXDB_INIT_ORG=polinetwork
- DOCKER_INFLUXDB_INIT_BUCKET=telegram_bot
- DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=tu_token_secreto_de_32_caracteres_o_mas
ports:
- "127.0.0.1:8086:8086"
environment:
- DOCKER_INFLUXDB_INIT_MODE=setup
- DOCKER_INFLUXDB_INIT_USERNAME=${INFLUXDB_INIT_USERNAME:?INFLUXDB_INIT_USERNAME must be set}
- DOCKER_INFLUXDB_INIT_PASSWORD=${INFLUXDB_INIT_PASSWORD:?INFLUXDB_INIT_PASSWORD must be set}
- DOCKER_INFLUXDB_INIT_ORG=polinetwork
- DOCKER_INFLUXDB_INIT_BUCKET=telegram_bot
- DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=${INFLUXDB_TOKEN:?INFLUXDB_TOKEN must be set}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker-compose.yml` around lines 18 - 26, The InfluxDB service configuration
currently hardcodes admin credentials in the compose setup and exposes the port
broadly, so update the docker-compose service to read sensitive values from
environment variables or an env file instead of embedding them directly. Use the
existing InfluxDB init settings in the service definition to map
`DOCKER_INFLUXDB_INIT_*` values from env-driven inputs, and ensure the app’s
`INFLUXDB_TOKEN` stays the single source of truth for telemetry auth to avoid
config drift. Identify the changes in the InfluxDB service block by its
`DOCKER_INFLUXDB_INIT_*` environment entries and `ports` section.

Comment thread package.json
"type": "module",
"scripts": {
"start": "NODE_ENV=production node dist/bot.js",
"start": "cross-env NODE_ENV=production node dist/bot.js",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether runtime scripts depend on dev-only binaries.
node - <<'NODE'
const pkg = require('./package.json')
const startUsesCrossEnv = /\bcross-env\b/.test(pkg.scripts?.start || '')
const inDeps = Boolean(pkg.dependencies?.['cross-env'])
const inDevDeps = Boolean(pkg.devDependencies?.['cross-env'])

console.log({ startUsesCrossEnv, inDeps, inDevDeps })
if (startUsesCrossEnv && !inDeps) {
  process.exitCode = 1
}
NODE

Repository: PoliNetworkOrg/telegram

Length of output: 221


Move cross-env into dependencies or remove it from start. npm ci --omit=dev won’t install the binary, so production startup will fail. (package.json:9)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` at line 9, The start script currently depends on cross-env, but
it may not be available in production installs. Update the package.json scripts
or dependency list so start no longer relies on a dev-only binary: either move
cross-env into dependencies or remove it from the start command and use a plain
NODE_ENV=production launch. Make the change in the start script entry so
production startup works after npm ci --omit=dev.

Comment thread package.json
"@grammyjs/runner": "^2.0.3",
"@influxdata/influxdb-client": "^1.35.0",
"@polinetwork/backend": "^0.15.18",
"@polinetwork/backend": "file:../backend/package",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether the local file dependency is resolvable in this checkout.
node - <<'NODE'
const fs = require('fs')
const path = require('path')
const pkg = require('./package.json')
const spec = pkg.dependencies?.['`@polinetwork/backend`']

console.log({ spec })
if (spec?.startsWith('file:')) {
  const target = path.resolve(process.cwd(), spec.slice('file:'.length))
  console.log({ target, exists: fs.existsSync(target) })
  if (!fs.existsSync(target)) process.exitCode = 1
}
NODE

Repository: PoliNetworkOrg/telegram

Length of output: 257


Avoid the sibling-local backend dependency. file:../backend/package only works when that neighboring path exists, so fresh installs in CI or published releases will fail to resolve @polinetwork/backend. Use a workspace dependency or a published semver version instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` at line 42, The package manifest currently depends on a
sibling-local backend path, which breaks installs when that neighboring
directory is absent. Update the dependency for `@polinetwork/backend` in
package.json to use a workspace reference or a published semver version instead
of the file:../backend/package path so fresh CI and release installs can resolve
it reliably.

Comment on lines +68 to +75
description: "Warn a user from a group",
scope: "both",
permissions: {
allowedRoles: ["owner", "direttivo"],
excludedRoles: ["creator"],
allowGroupAdmins: true,
},
handler: async ({ args, context, repliedTo }) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Restrict /warn to groups, or require an explicit target group.

With scope: "both", this can run in DMs, but the mutation always stores groupId: context.chat.id. In a private chat that records the DM as the warning’s group and can still drive the later penalty logic, which breaks the group-scoped warning model from the PR objective.

Also applies to: 90-95

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/moderation/warn.ts` around lines 68 - 75, The /warn command
currently allows DM execution via scope: "both", but the handler always persists
groupId from context.chat.id, so warnings in private chats get tied to the DM
instead of a real group. Update the moderation/warn.ts command definition and
handler so warn only runs in group chats or requires an explicit target group
before calling the warn mutation, using the existing handler, context, and
repliedTo flow to enforce a valid group-scoped groupId.

Comment thread src/commands/moderation/warn.ts
Comment thread src/commands/moderation/warn.ts
Comment on lines +168 to +171
try {
const res = await api.tg.warnings.deleteById.mutate({
id: warnId
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

/unwarn needs group-scoped authorization, not just a global warning ID.

This mutation only sends { id: warnId }, so the backend has no group context to verify that the warning belongs to the current chat. Any admin who can run /unwarn can remove warnings from another group by guessing IDs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/moderation/warn.ts` around lines 168 - 171, The /unwarn flow in
warn.ts only deletes by warnId, which allows cross-group deletion without chat
context. Update the moderation command handler around the deleteById mutate call
to also pass the current group/chat identifier, and make sure the backend
authorization path in the deleteById mutation verifies the warning belongs to
that chat before deleting. Use the existing warnId handling in this command plus
the current chat/group context available in the moderation command to scope the
request.

Comment thread src/utils/types.ts
Comment on lines +42 to +47
export const tgnumber = z
.string()
.refine((s) => !Number.isNaN(Number(s)) && s.trim() !== "", {
message: "Must be a valid numeric string",
})
.transform(Number);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Tighten this schema before using it for IDs.

This accepts values like -1, 1.5, 0x10, and Infinity. The current /unwarn caller uses it for a warning ID, so malformed IDs can reach deleteById instead of being rejected at parse time.

Suggested fix
 export const tgnumber = z
   .string()
-  .refine((s) => !Number.isNaN(Number(s)) && s.trim() !== "", {
-    message: "Must be a valid numeric string",
+  .trim()
+  .refine((s) => /^\d+$/.test(s), {
+    message: "Must be a valid positive integer string",
   })
   .transform(Number);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const tgnumber = z
.string()
.refine((s) => !Number.isNaN(Number(s)) && s.trim() !== "", {
message: "Must be a valid numeric string",
})
.transform(Number);
export const tgnumber = z
.string()
.trim()
.refine((s) => /^\d+$/.test(s), {
message: "Must be a valid positive integer string",
})
.transform(Number);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/types.ts` around lines 42 - 47, The tgnumber schema currently
accepts non-integer and non-decimal-edge values like negatives, floats, hex
strings, and Infinity, which is too loose for warning IDs. Tighten the
refinement in tgnumber so it only accepts valid finite positive integer strings
(or whatever exact ID shape the /unwarn caller expects) before transforming, and
keep the rejection at parse time instead of allowing malformed values through to
deleteById.

Comment thread src/utils/users.ts
Comment on lines +48 to +50
const user = await resolveUser(firstArg, context)
if (!user) {
return new Err("SILENT_ERROR")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Avoid double replies on lookup failures.

resolveUser() already sends the “user not found / cannot find this user” reply on this path. Returning Err("SILENT_ERROR") here makes /ban, /mute, and /warn emit a second generic error message for the same failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/users.ts` around lines 48 - 50, The lookup failure path in
resolveUser’s caller currently returns Err("SILENT_ERROR"), which causes a
second generic reply after resolveUser() already handles the “user not found”
case. Update the logic around the user resolution check to stop returning an
error there and instead exit early with no additional response, so commands like
ban, mute, and warn only send the single intended failure message.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Implement warning system

1 participant