Skip to content

Conversation

@lorenzocorallo
Copy link
Member

Enables Direttivo to grant user permissions to send not-allowed messages, in general to be whitelisted from the auto-moderation stack.
This can be used when Direttivo approves a promotional message to be sent across our groups, that might contain not-allowed links (e.g. forms.gle) that would cause the bot to delete the message.
We created a /grant command to use the backend grants create endpoint and we use the tgLogger to log CREATE, USAGE and INTERRUPT grant actions.

More customization grant creation will be possible in the WIP admin dashboard

Copilot AI review requested due to automatic review settings January 21, 2026 00:38
@coderabbitai
Copy link

coderabbitai bot commented Jan 21, 2026

Warning

Rate limit exceeded

@lorenzocorallo has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 9 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 041ecf5 and 12aff44.

📒 Files selected for processing (1)
  • src/commands/grants.ts

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds an interactive private /grant command and grant-management UI, integrates whitelisting into the auto-moderation stack, extends TgLogger with grant logging/menus/types, extracts a shared zod transformer, and bumps tooling deps and a TODO entry.

Changes

Cohort / File(s) Summary
Grant Command System
src/commands/grants.ts, src/commands/index.ts
New private /grant command with interactive menus (start/duration/confirm/cancel); resolves target, constructs grant (since/until/duration), calls backend to create grant, logs via TgLogger, registers command in index.
Grant Logging & UI
src/modules/tg-logger/grants.ts, src/modules/tg-logger/index.ts, src/modules/tg-logger/types.ts
New GrantLog type; adds grantMessageMenu and grantCreatedMenu; adds TgLogger.grants() method and grants topic counter to handle USAGE/CREATE/INTERRUPT flows and produce/forward messages.
Moderation with Whitelisting
src/middlewares/auto-moderation-stack/index.ts
Adds WhitelistType and ModerationContext<C>; middleware now checks whitelist, populates ctx.whitelisted, skips enforcement for creators/admins/grants or logs grant usages instead; handlers updated to use extended context.
Shared Utility Extraction
src/utils/types.ts, src/commands/banall.ts, src/commands/role.ts
Extracted and exported numberOrString zod transformer in src/utils/types.ts; banall.ts and role.ts now import and use it (removed local duplicates).
Dependency & Configuration Updates
biome.jsonc, package.json
Bumped Biome schema and @biomejs/biome to 2.3.11, updated @polinetwork/backend to ^0.13.1, and changed check:fix script to run biome check --unsafe.
Infrastructure & Misc
src/modules/index.ts, TODO.md
Added grants topic quota (402) to ModuleCoordinator/TgLogger config; updated TODO.md Direttivo moderation entry to checked and referenced /grant command.

Sequence Diagram(s)

sequenceDiagram
    participant User as Telegram User
    participant Bot as Bot (/grant)
    participant Backend as Backend API
    participant Logger as TgLogger
    participant Chat as Telegram Chat

    User->>Bot: /grant `@target` (open UI)
    Note over Bot: select start, time, duration, confirm
    Bot->>Backend: POST /grants (userId, adderId, reason, since, until)
    Backend-->>Bot: Grant created
    Bot->>Logger: Log CREATE grant
    Logger->>Chat: Post grant message / forward original
    Chat-->>User: Grant confirmation + management actions
Loading
sequenceDiagram
    participant Sender as Message Sender
    participant Middleware as Moderation Middleware
    participant Backend as Backend API
    participant Logger as TgLogger
    participant Chat as Telegram Chat

    Sender->>Middleware: Send message in group
    Middleware->>Backend: Check whitelist (role/admin/grants)
    Backend-->>Middleware: Whitelist status
    alt Whitelisted (creator/admin/grant)
        Middleware->>Logger: Log grant usage
        Middleware->>Chat: Skip enforcement or only log
    else Not whitelisted
        Middleware->>Chat: Mute/delete and/or reply
    end
Loading
🚥 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 'feat: grant user special permissions' directly reflects the main objective of the pull request, which is to enable granting users special permissions to bypass auto-moderation.

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


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.

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 pull request adds a grant system that allows Direttivo members to temporarily whitelist users from the auto-moderation stack. Users with active grants can send messages that would normally be blocked (e.g., promotional links, content flagged by AI moderation) without being muted or having their messages deleted.

Changes:

  • Added /grant command with an interactive menu for creating time-bound permission grants
  • Integrated grant checking into the auto-moderation whitelist system alongside role-based permissions
  • Added grant activity logging (CREATE, USAGE, INTERRUPT) with interactive menus in the TG logger

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/utils/types.ts Exported shared numberOrString Zod transformer for command argument parsing
src/modules/tg-logger/types.ts Added GrantLog discriminated union type for grant action logging
src/modules/tg-logger/index.ts Implemented grants() logging method and fixed formatting issue in moderation action logs
src/modules/tg-logger/grants.ts Created interactive menus for grant message handling and grant interruption
src/modules/index.ts Registered grants topic (402) for TG logger
src/middlewares/auto-moderation-stack/index.ts Added whitelist checking with grant support to link and harmful content handlers
src/commands/role.ts Refactored to use shared numberOrString helper
src/commands/index.ts Registered new grants command module
src/commands/grants.ts Implemented /grant command with interactive date/time/duration selection
src/commands/banall.ts Refactored to use shared numberOrString helper and fixed description
pnpm-lock.yaml Updated @polinetwork/backend (0.12.0 → 0.13.1) and @biomejs/biome (2.2.4 → 2.3.11)
package.json Updated dependencies and added --unsafe flag to biome check:fix script
biome.jsonc Updated schema reference to match new biome version
TODO.md Marked "do not delete Direttivo's allowed messages" as completed
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

src/middlewares/auto-moderation-stack/index.ts:241

  • The nonLatinHandler does not check for ctx.whitelisted before muting and deleting messages, unlike the linkHandler and harmfulContentHandler. This means that users with grants will still be moderated for non-latin character content, which is inconsistent with the feature's purpose of allowing whitelisted users to bypass auto-moderation. Consider adding a check similar to the other handlers to skip moderation for whitelisted users or log the grant usage if appropriate.
   */
  private async nonLatinHandler(ctx: Filter<ModerationContext<C>, "message:text" | "message:caption">) {
    const text = ctx.message.caption ?? ctx.message.text
    const match = text.match(NON_LATIN.REGEX)

    // 1. there are non latin characters
    // 2. there are more than LENGTH_THR non-latin characters
    // 3. the percentage of non-latin characters after the LENGTH_THR is more than PERCENTAGE_THR
    // that should catch messages respecting this inequality: 0.2y + 8 < x ≤ y
    // with x = number of non-latin characters, y = total length of the message
    // longer messages can have more non-latin characters, but less in percentage
    if (match && (match.length - NON_LATIN.LENGTH_THR) / text.length > NON_LATIN.PERCENTAGE_THR) {
      // just delete the message and mute the user for 10 minutes
      await mute({
        ctx,
        message: ctx.message,
        target: ctx.from,
        reason: "Message contains non-latin characters",
        duration: duration.zod.parse(NON_LATIN.MUTE_DURATION),
        from: ctx.me,
      })
    }

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

Copy link

@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

Caution

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

⚠️ Outside diff range comments (1)
src/middlewares/auto-moderation-stack/index.ts (1)

220-241: nonLatinHandler does not respect whitelist status.

Unlike linkHandler and harmfulContentHandler, this handler doesn't check ctx.whitelisted and will mute granted users for non-Latin content. This appears inconsistent with the PR objective to allow whitelisted users to bypass auto-moderation.

🐛 Suggested fix to respect whitelist
   private async nonLatinHandler(ctx: Filter<ModerationContext<C>, "message:text" | "message:caption">) {
+    if (ctx.whitelisted) return
     const text = ctx.message.caption ?? ctx.message.text
     const match = text.match(NON_LATIN.REGEX)

Alternatively, if granted users should still be logged for this action, follow the same pattern as linkHandler.

🤖 Fix all issues with AI agents
In `@package.json`:
- Around line 15-16: The "check:fix" npm script currently applies unsafe fixes;
update the scripts so "check:fix" runs only safe autofixes (remove the --unsafe
flag) and add a new "check:fix:unsafe" script that runs "biome check --write
--unsafe" for deliberate unsafe autofix workflows; modify the package.json
"scripts" entries for "check:fix" and add "check:fix:unsafe" accordingly so safe
fixes are the default and unsafe fixes are explicit.

In `@src/commands/grants.ts`:
- Around line 107-111: The changeDuration handler (and other handlers in this
file that call Telegram methods) fires async operations like ctx.editMessageText
and ctx.menu.nav without awaiting them, which can produce unhandled rejections
if the message was edited/deleted; update changeDuration to await
ctx.editMessageText(...) and await any async ctx.menu.* or Telegram API calls,
and wrap these awaits in a try/catch to swallow/log errors; apply the same
change to the other similar blocks in this file where ctx.editMessageText,
ctx.deleteMessage, ctx.reply or ctx.menu.nav are invoked so all Telegram calls
are awaited and errors handled.
🧹 Nitpick comments (2)
src/commands/grants.ts (1)

127-127: Resolve the timezone TODO before release.

Scheduling correctness depends on bot/user timezone alignment; happy to help implement this.

src/middlewares/auto-moderation-stack/index.ts (1)

93-105: Consider adding error handling for backend API calls.

If the backend API is unavailable, these calls will throw and could prevent moderation from running entirely. Consider wrapping in try-catch and defaulting to null (not whitelisted) on failure to ensure moderation remains active even during backend outages.

♻️ Suggested defensive approach
   private async isWhitelisted(ctx: ModerationContext<C>): Promise<WhitelistType | null> {
     const { status } = await ctx.getAuthor()
     if (status === "creator") return { role: "creator" }
     if (status === "administrator") return { role: "admin" }
 
-    const isAdmin = await api.tg.permissions.checkGroup.query({ userId: ctx.from.id, groupId: ctx.chatId })
-    if (isAdmin) return { role: "admin" }
-
-    const grant = await api.tg.grants.checkUser.query({ userId: ctx.from.id })
-    if (grant.isGranted) return { role: "user" }
+    try {
+      const isAdmin = await api.tg.permissions.checkGroup.query({ userId: ctx.from.id, groupId: ctx.chatId })
+      if (isAdmin) return { role: "admin" }
+
+      const grant = await api.tg.grants.checkUser.query({ userId: ctx.from.id })
+      if (grant.isGranted) return { role: "user" }
+    } catch {
+      // Backend unavailable - fail closed (not whitelisted) to maintain moderation
+    }
 
     return null
   }

Copy link

@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

Caution

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

⚠️ Outside diff range comments (1)
src/middlewares/auto-moderation-stack/index.ts (1)

221-241: Whitelist bypass is missing for non‑Latin moderation.
Whitelisted users can still be muted here, which conflicts with the new bypass semantics used in other handlers.

🛠 Proposed fix
   private async nonLatinHandler(ctx: Filter<ModerationContext<C>, "message:text" | "message:caption">) {
+    if (ctx.whitelisted) return
     const text = ctx.message.caption ?? ctx.message.text
🤖 Fix all issues with AI agents
In `@src/middlewares/auto-moderation-stack/index.ts`:
- Around line 94-105: The isWhitelisted method currently calls
api.tg.permissions.checkGroup.query and api.tg.grants.checkUser.query without
handling thrown errors, which can cause the middleware to fail closed; update
isWhitelisted (and keep ctx.getAuthor handling) to wrap the remote checks
(api.tg.permissions.checkGroup.query and api.tg.grants.checkUser.query) in
try/catch blocks and on any exception log or swallow the error and return null
(i.e. treat as not whitelisted) so moderation still runs; ensure the method
still returns { role: "creator" } or { role: "admin" } for ctx.getAuthor results
and { role: "user" } only when grant.isGranted is true, otherwise return null on
errors.
🧹 Nitpick comments (1)
src/middlewares/auto-moderation-stack/index.ts (1)

166-202: Confirm whitelist scope for harmful-content moderation.
Whitelisted users currently bypass mutes for harmful content (only logging USAGE). If grants are meant only to allow blocked links, consider still enforcing harmful-content actions or adding a scoped grant model.

Copilot AI review requested due to automatic review settings January 21, 2026 00:58
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 13 out of 14 changed files in this pull request and generated 2 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 21, 2026 01:24
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 13 out of 14 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

src/middlewares/auto-moderation-stack/index.ts:250

  • The nonLatinHandler does not check for whitelisted users before applying moderation actions. This is inconsistent with the other handlers (linkHandler, harmfulContentHandler, multichatSpamHandler) which all check ctx.whitelisted before taking action. Whitelisted users (admins and users with grants) should be allowed to bypass this check as well.
  private async nonLatinHandler(ctx: Filter<ModerationContext<C>, "message:text" | "message:caption">) {
    const text = ctx.message.caption ?? ctx.message.text
    const match = text.match(NON_LATIN.REGEX)

    // 1. there are non latin characters
    // 2. there are more than LENGTH_THR non-latin characters
    // 3. the percentage of non-latin characters after the LENGTH_THR is more than PERCENTAGE_THR
    // that should catch messages respecting this inequality: 0.2y + 8 < x ≤ y
    // with x = number of non-latin characters, y = total length of the message
    // longer messages can have more non-latin characters, but less in percentage
    if (match && (match.length - NON_LATIN.LENGTH_THR) / text.length > NON_LATIN.PERCENTAGE_THR) {
      // just delete the message and mute the user for 10 minutes
      await mute({
        ctx,
        message: ctx.message,
        target: ctx.from,
        reason: "Message contains non-latin characters",
        duration: duration.zod.parse(NON_LATIN.MUTE_DURATION),
        from: ctx.me,
      })
    }

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

Copy link

@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

Caution

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

⚠️ Outside diff range comments (1)
src/middlewares/auto-moderation-stack/index.ts (1)

230-250: Whitelist is ignored in non‑Latin handler.

Whitelisted users (admins/granted) can still be muted here, which breaks the “skip if whitelisted” policy used elsewhere. Consider short‑circuiting (or logging grant usage) like the other handlers.

🛠️ Suggested fix
   private async nonLatinHandler(ctx: Filter<ModerationContext<C>, "message:text" | "message:caption">) {
+    if (ctx.whitelisted) return
     const text = ctx.message.caption ?? ctx.message.text
     const match = text.match(NON_LATIN.REGEX)
🤖 Fix all issues with AI agents
In `@src/commands/grants.ts`:
- Around line 163-168: The "Now" menu item captures convNow at creation which
can become stale and also fails to await changeStartTime; update the handler on
the menu created via conversation.menu("grants-start-time", ...) .text(...) so
it recomputes the current time inside an async handler (call await
conversation.now() or new Date() there), derive hours/minutes from that fresh
time, and await the call to changeStartTime(ctx, hours, minutes) so the selected
"Now" always uses the current time and the async operation is properly awaited.
- Around line 213-223: The grant creation API call (api.tg.grants.create.mutate)
inside the .text("✅ Confirm", async (ctx) => { ... }) handler is non‑idempotent
and must be executed inside conversation.external to avoid duplicate grants on
replay; update the handler to call await conversation.external(async () => {
return await api.tg.grants.create.mutate({...}).catch(...) }) so the external
call is isolated from conversation replay/resume logic and returns the same {
success, error } shape to the surrounding code.

In `@src/middlewares/auto-moderation-stack/index.ts`:
- Around line 59-68: The filter callback dereferences ctx.from.id which can be
undefined for channel/anonymous posts; update the callback in the filter used
here to first check for a missing ctx.from (e.g., if (!ctx.from) return true)
before comparing ctx.from.id to ctx.me.id, then proceed to call
this.isWhitelisted and set ctx.whitelisted as before; ensure you reference the
same filter callback, ctx.from, ctx.me, isWhitelisted and ctx.whitelisted
symbols so the short‑circuit prevents a crash.
♻️ Duplicate comments (1)
src/commands/grants.ts (1)

107-111: Await menu-triggered Telegram API calls to avoid unhandled rejections.

Several handlers call ctx.editMessageText / res.deleteMessage without await. If the message is already edited/deleted, these reject and can surface as unhandled rejections. Please await and handle failures; apply the same pattern across menu callbacks.

🔧 Example fix (apply similarly elsewhere)
 async function changeDuration(ctx: ConversationMenuContext<ConversationContext<"private">>, durationStr: string) {
   grantDuration = duration.zod.parse(durationStr)
-  ctx.editMessageText(baseMsg(), { reply_markup: ctx.msg?.reply_markup })
-  ctx.menu.nav("grants-main")
+  await ctx.editMessageText(baseMsg(), { reply_markup: ctx.msg?.reply_markup }).catch(() => {})
+  await ctx.menu.nav("grants-main")
 }

Also applies to: 113-119, 122-131, 145-155, 161-162, 188-191

@lorenzocorallo lorenzocorallo merged commit 2b60187 into main Jan 21, 2026
2 checks passed
@lorenzocorallo lorenzocorallo deleted the whitelist branch January 21, 2026 01:45
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.

3 participants