Skip to content

refactor: new moderation stack#42

Merged
lorenzocorallo merged 22 commits intomainfrom
moderation-refactor
Jan 30, 2026
Merged

refactor: new moderation stack#42
lorenzocorallo merged 22 commits intomainfrom
moderation-refactor

Conversation

@lorenzocorallo
Copy link
Member

@lorenzocorallo lorenzocorallo commented Sep 20, 2025

This is the main idea behind this refactor (click to enlarge)

Untitled-2025-05-27-1910

@lorenzocorallo lorenzocorallo marked this pull request as draft September 20, 2025 17:31
Base automatically changed from log-refactor to main September 23, 2025 15:52
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 21, 2026

Walkthrough

Centralizes moderation into a new Moderation singleton with ban/kick/mute/unmute/multiChatSpam/deleteMessages/preDelete; migrates command handlers, middlewares, and tg-logger to use Moderation methods; removes per-action modules and UIActionsLogger; adds user mapping/storage helpers and PreDeleteResult semantics; bumps @polinetwork/backend to ^0.15.3.

Changes

Cohort / File(s) Summary
Dependency
package.json
Bumped @polinetwork/backend from ^0.15.2 to ^0.15.3.
Moderation core (new singleton)
src/modules/moderation/index.ts, src/modules/moderation/types.ts
Added Moderation singleton implementing centralized moderation workflow (validation, audit, perform, preDelete/deleteMessages) and public methods: ban, unban, kick, mute, unmute, multiChatSpam, deleteMessages. Introduced ModerationAction, PreDeleteResult, and ModerationError types.
Removed per-action modules
src/modules/moderation/ban.ts, src/modules/moderation/kick.ts, src/modules/moderation/mute.ts
Deleted standalone ban/kick/mute modules and their interfaces; logic migrated into the new Moderation singleton.
Command handlers
src/commands/...
src/commands/ban.ts, src/commands/kick.ts, src/commands/mute.ts, src/commands/del.ts
Replaced free-function calls with Moderation.* calls; updated imports and argument types (e.g., numberOrString for unban/unmute); /del now uses Moderation.deleteMessages and wait; replies standardized to "OK" or formatted error with timed deletion.
Auto-moderation & group hooks
src/middlewares/...
src/middlewares/auto-moderation-stack/index.ts, src/middlewares/group-specific-actions.ts
Replaced local mute/multi-chat/delete logic with Moderation.mute / Moderation.multiChatSpam / Moderation.deleteMessages; handlers now inspect res.isOk()/res.isErr() and surface res.error with added error logging.
TgLogger & reporting
src/modules/tg-logger/...
src/modules/tg-logger/index.ts, src/modules/tg-logger/types.ts, src/modules/tg-logger/report.ts, src/modules/tg-logger/grants.ts
Refactored deletion into preDelete(...) returning `PreDeleteResult
User utilities & storage
src/utils/..., src/middlewares/...
src/utils/users.ts, src/utils/types.ts, src/middlewares/message-user-storage.ts
Added exported getUser helper, toGrammyUser mapper, and MessageUserStorage.getStoredUser to fetch cached/backend users and map to Grammy User.
Types & duration
src/utils/duration.ts, src/modules/tg-logger/types.ts
Exported and extended Duration type (added secondsFromNow, dateStr) and added duration.fromSeconds; renamed/updated delete result semantics to PreDeleteResult (includes logMessageIds) and integrated into moderation types.
Middleware stack changes
src/bot.ts, src/middlewares/ui-actions-logger.ts
Replaced UIActionsLogger middleware with Moderation middleware; removed UIActionsLogger implementation.
Misc / logging flows
src/modules/tg-logger/*, src/middlewares/*
Adjusted callsites to handle Result-returning moderation methods, unified moderation log title rendering, added error logging where moderation operations can fail, and timed-deletion of temporary replies.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User as User
  participant Cmd as CommandHandler
  participant Mod as Moderation
  participant TgLog as TgLogger
  participant API as Telegram/Backend

  User->>Cmd: trigger moderation command / report (with replied message)
  Cmd->>Mod: call Moderation.*(target, chat, issuer, duration?, [msg], reason)
  Mod->>TgLog: preDelete([msg], reason, issuer)
  TgLog-->>Mod: PreDeleteResult | null
  Mod->>API: perform action(s) (delete, ban/kick/mute, multi-chat ops)
  API-->>Mod: success / error
  Mod->>TgLog: moderationAction(payload including preDeleteRes)
  Mod-->>Cmd: Result.ok / Result.err
  Cmd-->>User: reply "OK" or error (deleted after delay)
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 1 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor: new moderation stack' clearly describes the main objective of the changeset. The PR involves a comprehensive refactoring that centralizes moderation functionality into a new Moderation class, consolidates moderation-related imports and APIs, and removes scattered moderation handlers. The title accurately reflects the primary change.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

@lorenzocorallo
Copy link
Member Author

lorenzocorallo commented Jan 29, 2026

reopen

@lorenzocorallo lorenzocorallo marked this pull request as ready for review January 29, 2026 20:48
@lorenzocorallo lorenzocorallo requested review from Copilot and removed request for Copilot January 29, 2026 20:48
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/modules/tg-logger/report.ts (1)

82-116: Check Result types from moderation actions before marking reports resolved.

All three moderation methods return Result types (deleteMessages returns Result<PreDeleteResult | null, "DELETE_ERROR">, while kick and ban return Result<void, string>). The current code at lines 82–116 awaits these calls but ignores the returned Result, proceeding directly to editReportMessage regardless of success or failure. If a moderation action fails, the report will still be marked as resolved, leaving the moderation action incomplete.

Add error handling:

  • Check the Result before calling editReportMessage
  • Return feedback to the user if the action fails (e.g., "❌ Failed to delete message")
  • Only mark the report resolved after confirming success
src/commands/mute.ts (1)

83-90: Handle NaN from parseInt when username is not a valid numeric ID.

If args.username doesn't start with @ but is not a valid number (e.g., "abc"), parseInt returns NaN, which is truthy when checked with !userId. However, NaN would then be passed to getUser, potentially causing unexpected behavior.

🛡️ Proposed fix to validate numeric input
-      const userId = args.username.startsWith("@") ? await getTelegramId(args.username) : parseInt(args.username, 10)
-      if (!userId) {
+      const userId = args.username.startsWith("@") ? await getTelegramId(args.username) : parseInt(args.username, 10)
+      if (!userId || Number.isNaN(userId)) {
src/commands/ban.ts (1)

83-90: Handle NaN from parseInt when username is not a valid numeric ID.

Same issue as in mute.ts — if args.username is not numeric, parseInt returns NaN.

🛡️ Proposed fix
       const userId = args.username.startsWith("@") ? await getTelegramId(args.username) : parseInt(args.username, 10)
-      if (!userId) {
+      if (!userId || Number.isNaN(userId)) {
🤖 Fix all issues with AI agents
In `@src/middlewares/auto-moderation-stack/index.ts`:
- Around line 196-213: The comment incorrectly states the duration is one
minute; update the misleading comment near the Moderation.mute call that uses
duration.zod.parse("1d") to reflect it represents one day (or change the
argument to the intended duration), e.g., correct or remove the inline comment
by locating duration.zod.parse("1d") in the auto-moderation stack code (around
Moderation.mute and the subsequent ctx.reply) and ensure the comment accurately
states "1 day" (or replace "1d" with the proper value if the intent was one
minute).
- Around line 245-252: Moderation.mute is returning a Result but its error is
being ignored; update the call in the auto-moderation stack to handle the Result
from Moderation.mute (e.g., inspect the returned Result or use .ok/.err or
match) and on error log or propagate the failure (use processLogger or
ctx.logger and include the error and context like ctx.from/ctx.chat/ctx.me), and
also update or remove the stale duration comment by replacing the misleading "1
minute" note next to duration.zod.parse(NON_LATIN.MUTE_DURATION) with an
accurate description or none at all so comments reflect the actual value.

In `@src/middlewares/group-specific-actions.ts`:
- Around line 69-73: The call to ctx.deleteMessage() can throw and break the
middleware flow; wrap the await ctx.deleteMessage() in a try/catch so deletion
failures are caught and do not prevent the following user-facing actions. In the
catch block, log the error via the same logger used above
(modules.get("tgLogger")) including relevant context (e.g., ctx.message or
message id and the caught error) and do not rethrow so the middleware continues
normally. Ensure you still await the delete inside the try block and keep the
preDelete call (preDelete([...]) and ctx as they are.

In `@src/modules/moderation/index.ts`:
- Around line 194-196: The error message in multiChatSpam (function
multiChatSpam(target, messagesToDelete, duration)) uses an unprofessional string
"Sei stupido"; replace it with a clear, professional error description (e.g.,
"no messages to delete" or "messagesToDelete is empty") and ensure the returned
err() includes context about which parameter triggered it (messagesToDelete) so
logs and user-facing messages are appropriate and informative.
- Around line 89-101: The MULTI_CHAT_SPAM case is calling
modules.shared.api.restrictChatMember with p.from.id (the bot) instead of the
offender; change the call to use p.target.id so the restriction is applied to
the spammer. Update the MULTI_CHAT_SPAM branch that iterates
groupMessagesByChat(p.messages).keys() and ensure
modules.shared.api.restrictChatMember(chatId, p.target.id,
RestrictPermissions.mute, { until_date: p.duration.timestamp_s }) is used
(preserving the existing .catch(() => false) and Promise.all/.then logic).

In `@src/utils/users.ts`:
- Around line 15-18: The current logic around api.tg.users.get.query
destructures { user, error } and returns err(error) when user is falsy, which
can pass undefined errors to callers; update the return path in the function
that calls api.tg.users.get.query so that when neither user nor error is present
you return a meaningful fallback error (e.g., use err(error ?? new Error("user
not found")) or similar), keeping the ok(toGrammyUser(user)) branch unchanged;
reference symbols: api.tg.users.get.query, user, error, toGrammyUser, ok, err.
🧹 Nitpick comments (7)
src/commands/del.ts (1)

24-28: Prefer Promise.allSettled so one deletion failure doesn’t abort the other.

This makes /del more robust if one message is already gone or deletion permissions are partial.

♻️ Safer deletion pattern
-    await Promise.all([
-      context.deleteMessages([repliedTo.message_id]),
-      context.deleteMessage(), // /del message
-    ])
+    await Promise.allSettled([
+      context.deleteMessages([repliedTo.message_id]),
+      context.deleteMessage(), // /del message
+    ])
src/commands/mute.ts (1)

92-99: Consider passing context to getUser for potential optimization.

The getUser function can use ctx.getChatMember() as a faster lookup before falling back to the API. Passing context here could avoid an API call when the user is a member of the current chat.

-      const user = await getUser(userId)
+      const user = await getUser(userId, context)
src/commands/ban.ts (2)

54-57: Minor: Logger message says "ban" but this is the "tban" handler.

The error message references "ban" instead of "tban", which could cause confusion when debugging logs.

       if (!repliedTo.from) {
-        logger.error("ban: no repliedTo.from field (the msg was sent in a channel)")
+        logger.error("tban: no repliedTo.from field (the msg was sent in a channel)")
         return
       }

92-99: Consider passing context to getUser for potential optimization.

Same as in mute.ts — passing context allows getUser to use getChatMember before falling back to the API.

-      const user = await getUser(userId)
+      const user = await getUser(userId, context)
src/modules/moderation/index.ts (3)

35-35: Typo: "youself" should be "yourself".

-    if (p.target.id === p.from.id) return err(fmt(({ b }) => b`@${p.from.username} you cannot moderate youself (smh)`))
+    if (p.target.id === p.from.id) return err(fmt(({ b }) => b`@${p.from.username} you cannot moderate yourself (smh)`))

114-116: Address TODO: Clean up preDelete messages on deletion failure.

The TODO indicates that if message deletion fails, the preDelete log messages should also be cleaned up to avoid orphaned log entries. Consider tracking this as a follow-up task.

Would you like me to open an issue to track implementing cleanup of preDelete messages when deletion fails?


65-70: Consider using Math.floor for until_date to ensure integer seconds.

Date.now() / 1000 produces a float. While Telegram's API typically handles this, using Math.floor ensures a clean integer and matches the pattern used elsewhere (e.g., line 30 in duration.ts).

       case "KICK":
         return modules.shared.api
           .banChatMember(p.chat.id, p.target.id, {
-            until_date: Date.now() / 1000 + duration.values.m,
+            until_date: Math.floor(Date.now() / 1000) + duration.values.m,
           })
           .catch(() => false)

Copilot AI review requested due to automatic review settings January 29, 2026 21:25
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/middlewares/auto-moderation-stack/index.ts`:
- Around line 205-212: The reply currently sends raw res.error to users via
ctx.reply in the auto-moderation handler (see the ctx.reply call inside
src/middlewares/auto-moderation-stack/index.ts and the res object), so replace
the user-facing text with a generic, friendly fallback (e.g., "Message removed
for safety — if you think this is a mistake contact admins") while logging the
actual error internally (use your existing logger or processLogger.error and
include res.error and contextual info like ctx.from, chat id and message id);
ensure the fmt/formatting path still supports the fallback message and do not
expose raw res.error to users.
- Around line 159-167: The code currently passes the raw res.error into
ctx.reply (in the msg creation using fmt and fmtUser), which can leak internal
details; change the reply to show a generic, user-facing message like "An error
occurred while processing your request. Please try again later." while logging
the actual error from res.error via the existing logger (or throw to a monitored
handler). Update the branch where res.isOk() is false (the else branch that uses
res.error) so it doesn't expose res.error to users but records res.error using
the service-level logger or error reporter associated with the
middleware/Moderation.mute flow.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This refactor introduces a centralized moderation stack (Moderation class) that handles Telegram moderation actions, logging, and message deletion in a unified way, and wires existing commands and middlewares into this new flow.

Changes:

  • Add Moderation singleton to encapsulate validation, Telegram API calls, deletion, and audit logging for mute/ban/kick/unban/unmute and multi-chat spam actions, plus a getUser helper that resolves users via chat or backend.
  • Refactor tg-logger to separate “pre-delete” logging from actual message deletion and to consume preDeleteRes links from the new moderation flow.
  • Update command handlers (/ban, /mute, /kick, /del, unban/unmute) and automated middlewares (auto-moderation stack, group-specific actions, grants/report handlers) to use the new moderation APIs and backend version.

Reviewed changes

Copilot reviewed 18 out of 19 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/utils/users.ts Adds getUser utility that tries ctx.getChatMember then falls back to api.tg.users.get, used by unban/unmute commands.
src/utils/types.ts Imports ApiOutput and adds toGrammyUser to map backend user records to grammy User, plus re-exports Context/ModuleShared helpers.
src/utils/duration.ts Exports the Duration type so it can be reused by the moderation stack.
src/modules/tg-logger/types.ts Extends ModerationAction with optional preDeleteRes and renames DeleteResult to PreDeleteResult used for deleted-message links.
src/modules/tg-logger/index.ts Introduces MOD_ACTION_TITLE, adds preDelete logging (without deleting messages), and updates moderationAction to use preDeleteRes for “See Deleted Message” links.
src/modules/tg-logger/report.ts Refactors report menu actions to call Moderation.deleteMessages/Moderation.kick/Moderation.ban instead of hand-rolled API calls.
src/modules/tg-logger/grants.ts Uses Moderation.deleteMessages for deleting granted users’ messages, mapping errors to grant-menu feedback.
src/modules/moderation/index.ts New Moderation singleton centralizes target validation, audit logging, executing moderation actions, pre-delete logging, and grouped deleteMessages.
src/middlewares/group-specific-actions.ts Switches from tgLogger.delete to tgLogger.preDelete plus explicit ctx.deleteMessage when group-specific rules are violated.
src/middlewares/auto-moderation-stack/index.ts Rewires link, harmful-content, non-latin, and multi-chat-spam handlers to Moderation.mute/Moderation.multiChatSpam, and tailors user-facing replies based on Result success/failure.
src/commands/mute.ts Replaces direct mute/unmute helpers with Moderation.mute/Moderation.unmute, uses getUser for unmute, and normalizes feedback to “OK” or error text.
src/commands/kick.ts Replaces kick helper with Moderation.kick and standardizes ephemeral success/error reply behavior.
src/commands/del.ts Uses tgLogger.preDelete for logging then deletes both the target and /del command messages via deleteMessages/deleteMessage.
src/commands/ban.ts Replaces ban/unban helpers with Moderation.ban/Moderation.unban, uses getUser for unban, and standardizes success/error responses.
src/modules/moderation/ban.ts Removed in favor of centralized moderation logic in src/modules/moderation/index.ts.
src/modules/moderation/mute.ts Removed in favor of centralized moderation logic in src/modules/moderation/index.ts.
src/modules/moderation/kick.ts Removed in favor of centralized moderation logic in src/modules/moderation/index.ts.
src/modules/tg-logger/report.ts (same file as above) Updated menu callbacks to hook into new moderation stack for delete/kick/ban actions.
src/modules/tg-logger/grants.ts (same file as above) Updated delete flow to depend on Moderation.deleteMessages and propagate failures as menu errors.
package.json Bumps @polinetwork/backend dependency to ^0.15.3 to support new backend API usage.
pnpm-lock.yaml Locks @polinetwork/backend to 0.15.3 and updates its integrity hash to match the new version.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings January 29, 2026 23:01
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/modules/moderation/index.ts`:
- Around line 110-116: After a failed delete you must undo the earlier preDelete
work to avoid orphaned telemetry; call a rollback/compensating method on the
same logger with the preRes before returning the error. Specifically, after
modules.shared.api.deleteMessages returns falsy, invoke
modules.get("tgLogger").rollbackPreDelete(preRes) (guarding for preRes
truthiness) and catch/log any rollback errors (but do not throw) before
returning err("DELETE_ERROR"); ensure the rollback call is placed inside the
for-loop where deleteMessages failed so each affected chat's preDelete is
compensated.
- Around line 123-137: In moderate (private async moderate) we currently ignore
the result of deleteMessages (preDeleteRes) and proceed to perform the action;
check if preDeleteRes is an Err and immediately return that Err before calling
perform so deletion failures propagate; specifically, after calling
deleteMessages (the variable preDeleteRes) add a guard that if preDeleteRes !==
null and preDeleteRes.isErr() then return preDeleteRes; keep the existing
perform(p) flow otherwise.
- Around line 90-100: The code in MULTI_CHAT_SPAM uses
groupMessagesByChat(p.messages).keys().map(...), but Map.prototype.keys()
returns an iterator (no map), causing a runtime error; fix by converting the
keys iterator to an array (e.g.,
Array.from(groupMessagesByChat(p.messages).keys()) or
[...groupMessagesByChat(p.messages).keys()]) before calling .map, so the
subsequent calls to modules.shared.api.restrictChatMember(...) (and its .catch)
work as intended and the Promise.all result remains unchanged.

In the rare case when we need to get an user by userId,
the context cannot retrieve it and the user is yet to be synced
to the backend, we can retrieve it from the temporary map.
Copilot AI review requested due to automatic review settings January 29, 2026 23:16
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/utils/users.ts`:
- Around line 5-11: The getUser function uses optional chaining incorrectly
causing a TypeError when ctx is undefined; modify getUser so it first checks
whether ctx is provided and only then calls ctx.getChatMember(userId) (awaiting
and handling .then/.catch), otherwise skip the call and fall back to
MessageUserStorage.getInstance().getStoredUser(userId); refer to the getUser
function, Context type and MessageUserStorage.getInstance().getStoredUser to
locate and update the logic.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 19 out of 20 changed files in this pull request and generated 5 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings January 30, 2026 00:49
@lorenzocorallo lorenzocorallo review requested due to automatic review settings January 30, 2026 00:50
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/modules/moderation/index.ts`:
- Around line 62-66: In the PERFORM_ERROR case update the strError value to fix
the typo: change "There was an error perfoming the moderation action" to "There
was an error performing the moderation action" (the identifiers to edit are the
case "PERFORM_ERROR" object and its strError property; no other behavior changes
needed).
- Around line 154-160: The deleteMessages call in the error branch can reject
and produce an unhandled promise; update the fire-and-forget invocation of
modules.shared.api.deleteMessages(tgLogger.groupId, preRes.logMessageIds) to
explicitly handle rejections (e.g., attach a .catch(...) that swallows/logs the
error) so the rejection is consumed; keep the existing logger.error and return
err("DELETE_ERROR") behavior unchanged but ensure the deleteMessages promise
cannot become unhandled.
🧹 Nitpick comments (2)
src/modules/moderation/index.ts (1)

27-27: Address the TODO for in-channel moderation feedback.

If this is still planned, consider tracking it with an issue so it doesn’t get lost during the refactor.

If you want, I can draft the user-facing feedback flow and open an issue.

src/commands/del.ts (1)

26-30: Guard deletion of the temporary feedback message.

If msg.delete() fails (permissions/auto‑delete), it will throw after the response is sent. Consider swallowing the error.

♻️ Suggested tweak
-    await msg.delete()
+    await msg.delete().catch(() => {})

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/modules/moderation/index.ts`:
- Around line 39-67: The user-facing error message in getModerationError for the
"PERFORM_ERROR" case has a typo ("perfoming"); update the strError value in the
PERFORM_ERROR return object inside getModerationError to "There was an error
performing the moderation action" (referencing the getModerationError function
and the "PERFORM_ERROR" case to locate the code).
- Around line 71-77: In checkTargetValid, treat failures from
modules.shared.api.getChatMember as a closed/failing case instead of returning
null: replace the current .catch(() => null) by catching errors and returning an
appropriate Err (e.g., err("CANNOT_MOD_GROUPADMIN") or a dedicated API failure
code) so that a transient API error prevents the moderation action;
specifically, in the checkTargetValid function (handling ModerationAction p)
ensure that if getChatMember throws or yields no valid member you immediately
return an Err rather than skipping the admin/creator check.
- Around line 250-269: Change multiChatSpam to accept a non-empty messages tuple
instead of a plain array by updating the parameter type to [Message,
...Message[]] (so callers cannot pass an empty array), remove the runtime throw
that checks messagesToDelete.length, and keep the body calling
this.moderate(...) as-is; mirror the established pattern used by
deleteMessages() to preserve the Promise<Result<void, ModerationError>> contract
and avoid throwing.
🧹 Nitpick comments (1)
src/modules/moderation/index.ts (1)

27-27: Track the TODO for in-channel feedback.
If this is still desired, consider opening an issue so it doesn’t get lost.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@toto04
Copy link
Contributor

toto04 commented Jan 30, 2026

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 30, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/modules/moderation/index.ts`:
- Around line 163-174: The log payload uses a misspelled key "forwaredCount";
update the logger.warn call to rename that property to "forwardedCount" so the
emitted log includes the correct field name (leave other fields like
initialMessages, executor, deletedCount, deletedPercentage and the condition
using preRes.count and delCount unchanged), i.e., change the object literal
passed to logger.warn to use forwardedCount: preRes.count instead of
forwaredCount.
🧹 Nitpick comments (2)
src/modules/moderation/index.ts (2)

81-92: Consider adding error handling for audit failures.

The api.tg.auditLog.create.mutate() call has no error handling. If the audit API fails, the moderation action completes successfully but the audit trail is lost without any indication. Consider adding .catch() to at least log audit failures.

🔧 Suggested improvement
   private async audit(p: ModerationAction) {
     if (p.action === "SILENT" || p.action === "MULTI_CHAT_SPAM") return
 
-    await api.tg.auditLog.create.mutate({
+    await api.tg.auditLog.create.mutate({
       adminId: p.from.id,
       groupId: p.chat.id,
       targetId: p.target.id,
       type: MAP_ACTIONS[p.action],
       until: "duration" in p && p.duration ? p.duration.date : null,
       reason: "reason" in p ? p.reason : undefined,
-    })
+    }).catch((e) => logger.error({ error: e, action: p }, "[Moderation:audit] failed to create audit log"))
   }

148-152: Variable ok shadows imported ok from neverthrow.

The boolean variable ok on line 150 shadows the ok function imported from neverthrow on line 2. While not a bug (the import is still accessible before this scope), it reduces code clarity.

♻️ Rename for clarity
     let delCount = 0
     for (const [chatId, mIds] of groupMessagesByChat(messages)) {
-      const ok = await modules.shared.api.deleteMessages(chatId, mIds).catch(() => false)
-      if (ok) delCount += mIds.length
+      const deleted = await modules.shared.api.deleteMessages(chatId, mIds).catch(() => false)
+      if (deleted) delCount += mIds.length
     }

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/middlewares/group-specific-actions.ts`:
- Around line 69-77: The deleteMessages result is logged but not acted on, which
lets the middleware continue and send the user-facing reply; change
GroupSpecificActions so that after calling Moderation.deleteMessages if
res.isErr() you abort the flow (e.g., throw res.error or rethrow a new Error)
instead of only logging—this preserves the fail-fast atomic moderation behavior
and prevents subsequent calls like ctx.reply from running when deletion fails.

@lorenzocorallo lorenzocorallo merged commit 6d4ae93 into main Jan 30, 2026
2 checks passed
@lorenzocorallo lorenzocorallo deleted the moderation-refactor branch January 30, 2026 02:31
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.

2 participants