Skip to content

Conversation

@lorenzocorallo
Copy link
Member

@lorenzocorallo lorenzocorallo commented Oct 17, 2025

Summary by CodeRabbit

  • Documentation

    • Updated project reference links
    • Added new reference materials to README
  • New Features

    • Introduced group-specific message validation with automated error notifications

✏️ Tip: You can customize this high-level summary in your review settings.

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

Adds a new middleware to enforce group-specific posting rules based on required hashtags, and wires it into the bot pipeline.

  • Introduces GroupSpecificActions middleware with per-group hashtag validation and user feedback.
  • Integrates the middleware into bot.ts to run after existing moderation.
  • Updates TODO to mark the feature complete and adds a small README reference.

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
src/middlewares/group-specific-actions.ts Implements group-specific message validation and error handling for configured groups.
src/bot.ts Registers the new GroupSpecificActions middleware in the bot middleware chain.
TODO.md Marks group-specific moderation as completed.
README.md Adds a reference link; no functional changes.

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +61 to +103
checkAlloggi(ctx: Filter<C, "message">): Result<void, string> {
const hashtags = ctx.entities("hashtag").map((e) => e.text.toLowerCase())

if (
!hashtags.includes("#cerco") &&
!hashtags.includes("#searching") &&
!hashtags.includes("#search") &&
!hashtags.includes("#offro") &&
!hashtags.includes("#offering") &&
!hashtags.includes("#offer")
)
return err(
"You must include one of the following hashtags in your message:\n #cerco #searching #offro #offering \nCheck rules for more info."
)

return ok()
}

checkRipetizioni(ctx: Filter<C, "message">): Result<void, string> {
const hashtags = ctx.entities("hashtag").map((e) => e.text.toLowerCase())

if (
!hashtags.includes("#richiesta") &&
!hashtags.includes("#offerta") &&
!hashtags.includes("#request") &&
!hashtags.includes("#offer")
)
return err(
"You must include one of the following hashtags in your message:\n #richiesta #request #offerta #offer \nCheck rules for more info."
)

return ok()
}

checkBooks(ctx: Filter<C, "message">): Result<void, string> {
const hashtags = ctx.entities("hashtag").map((e) => e.text.toLowerCase())

if (!hashtags.includes("#cerco") && !hashtags.includes("#vendo"))
return err(
"Devi includere uno di questi hashtags nel tuo messaggio:\n #cerco #vendo \nControlla le regole per maggiori indicazioni."
)

return ok()
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

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

[nitpick] The three check methods duplicate the same pattern. Consider extracting a reusable helper, e.g. checkHashtags(ctx, allowed, errorMessage), to centralize the entity extraction and membership logic and make future changes safer.

Suggested change
checkAlloggi(ctx: Filter<C, "message">): Result<void, string> {
const hashtags = ctx.entities("hashtag").map((e) => e.text.toLowerCase())
if (
!hashtags.includes("#cerco") &&
!hashtags.includes("#searching") &&
!hashtags.includes("#search") &&
!hashtags.includes("#offro") &&
!hashtags.includes("#offering") &&
!hashtags.includes("#offer")
)
return err(
"You must include one of the following hashtags in your message:\n #cerco #searching #offro #offering \nCheck rules for more info."
)
return ok()
}
checkRipetizioni(ctx: Filter<C, "message">): Result<void, string> {
const hashtags = ctx.entities("hashtag").map((e) => e.text.toLowerCase())
if (
!hashtags.includes("#richiesta") &&
!hashtags.includes("#offerta") &&
!hashtags.includes("#request") &&
!hashtags.includes("#offer")
)
return err(
"You must include one of the following hashtags in your message:\n #richiesta #request #offerta #offer \nCheck rules for more info."
)
return ok()
}
checkBooks(ctx: Filter<C, "message">): Result<void, string> {
const hashtags = ctx.entities("hashtag").map((e) => e.text.toLowerCase())
if (!hashtags.includes("#cerco") && !hashtags.includes("#vendo"))
return err(
"Devi includere uno di questi hashtags nel tuo messaggio:\n #cerco #vendo \nControlla le regole per maggiori indicazioni."
)
return ok()
private checkHashtags(
ctx: Filter<C, "message">,
allowed: string[],
errorMessage: string
): Result<void, string> {
const hashtags = ctx.entities("hashtag").map((e) => e.text.toLowerCase())
if (!hashtags.some((tag) => allowed.includes(tag))) {
return err(errorMessage)
}
return ok()
}
checkAlloggi(ctx: Filter<C, "message">): Result<void, string> {
return this.checkHashtags(
ctx,
[
"#cerco",
"#searching",
"#search",
"#offro",
"#offering",
"#offer",
],
"You must include one of the following hashtags in your message:\n #cerco #searching #offro #offering \nCheck rules for more info."
)
}
checkRipetizioni(ctx: Filter<C, "message">): Result<void, string> {
return this.checkHashtags(
ctx,
[
"#richiesta",
"#offerta",
"#request",
"#offer",
],
"You must include one of the following hashtags in your message:\n #richiesta #request #offerta #offer \nCheck rules for more info."
)
}
checkBooks(ctx: Filter<C, "message">): Result<void, string> {
return this.checkHashtags(
ctx,
[
"#cerco",
"#vendo",
],
"Devi includere uno di questi hashtags nel tuo messaggio:\n #cerco #vendo \nControlla le regole per maggiori indicazioni."
)

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

Up to @toto04

Copilot AI review requested due to automatic review settings October 17, 2025 23:43
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 4 out of 4 changed files in this pull request and generated 4 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +25 to +30
if (ctx.from.id === ctx.me.id) return next() // skip if bot
const { roles } = await api.tg.permissions.getRoles.query({ userId: ctx.from.id })
if (roles && roles.length > 0) return next() // skip if admin or other roles

const chatMember = await ctx.getChatMember(ctx.from.id)
if (chatMember.status === "administrator" || chatMember.status === "creator") return next() // skip if group-admin
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

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

ctx.from can be undefined for messages sent on behalf of a channel (sender_chat) or anonymous admins in supergroups, causing a runtime error when accessing ctx.from.id. Add a guard (e.g., if (!ctx.from) return next()) before using ctx.from and use optional chaining for the bot check; also avoid calling getChatMember when ctx.from is absent.

Copilot uses AI. Check for mistakes.
!hashtags.includes("#offer")
)
return err(
"You must include one of the following hashtags in your message:\n #cerco #searching #offro #offering \nCheck rules for more info."
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

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

The error message for alloggi omits hashtags that are accepted by code (#search and #offer). Update the message to list all accepted tags, e.g., '... #cerco #searching #search #offro #offering #offer ...' to match the validation logic.

Suggested change
"You must include one of the following hashtags in your message:\n #cerco #searching #offro #offering \nCheck rules for more info."
"You must include one of the following hashtags in your message:\n #cerco #searching #search #offro #offering #offer \nCheck rules for more info."

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +14
const TARGET_GROUPS: Record<string, number> = {
alloggi: -1001175999519,
ripetizioni: -1001495422899,
books: -1001164044303,
} as const
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

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

[nitpick] Hard-coded group IDs make deployments and environment changes brittle. Consider moving these IDs to configuration (e.g., env variables or a config file) and parsing them at startup to avoid code changes per environment.

Copilot uses AI. Check for mistakes.
Comment on lines +71 to +87
checkAlloggi(ctx: Filter<C, "message">): Result<void, string> {
const hashtags = ctx.entities("hashtag").map((e) => e.text.toLowerCase())

if (
!hashtags.includes("#cerco") &&
!hashtags.includes("#searching") &&
!hashtags.includes("#search") &&
!hashtags.includes("#offro") &&
!hashtags.includes("#offering") &&
!hashtags.includes("#offer")
)
return err(
"You must include one of the following hashtags in your message:\n #cerco #searching #offro #offering \nCheck rules for more info."
)

return ok()
}
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

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

[nitpick] The hashtag validation logic is duplicated across checkAlloggi, checkRipetizioni, and checkBooks, which risks drift (already visible in the mismatched message). Consider extracting a generic helper like checkHashtags(ctx, allowed, errorMessage) and driving it from a per-group config map to keep logic and messages in sync.

Copilot uses AI. Check for mistakes.
@lorenzocorallo
Copy link
Member Author

@coderabbitai full review

@coderabbitai
Copy link

coderabbitai bot commented Nov 29, 2025

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link

coderabbitai bot commented Nov 29, 2025

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

This pull request adds a new middleware to enforce group-specific hashtag validation rules for Telegram groups. It includes updates to documentation references, integrates the middleware into the bot's middleware stack, and implements validators for three distinct group types with logging and automatic error message cleanup.

Changes

Cohort / File(s) Summary
Documentation updates
README.md, TODO.md
Added "Maybe useful references" section to README with private messages reference link; updated organization reference in TODO.md link from PoliNetworkBot_CSharp to PoliNetworkOrg/PoliNetworkBot_CSharp.
Middleware registration
src/bot.ts
Registered new GroupSpecificActions middleware in the bot's middleware stack, positioned after AutoModerationStack and before UIActionsLogger.
Group-specific validation middleware
src/middlewares/group-specific-actions.ts
New middleware implementing target-group filtering, multi-step message validation (admin/creator/role exclusions), and group-specific hashtag enforcement via three validators (checkAlloggi, checkRipetizioni, checkBooks). Logs violations, replies with formatted error messages, and schedules automatic deletion after 2 minutes.

Sequence Diagram

sequenceDiagram
    participant User
    participant Bot as Bot/Middleware
    participant GSA as GroupSpecificActions
    participant Logger
    participant Telegram

    User->>Bot: Send message
    Bot->>GSA: Process through middleware
    
    rect rgb(220, 240, 255)
        Note over GSA: Filter & Access Checks
        GSA->>GSA: Is target group?
        GSA->>GSA: Is bot/user with role?
        GSA->>GSA: Is admin/creator?
    end
    
    alt Access Check Passed
        rect rgb(200, 255, 220)
            Note over GSA: Validate Message
            GSA->>GSA: Dispatch to group validator<br/>(checkAlloggi/Ripetizioni/Books)
            GSA->>GSA: Check required hashtags
        end
        
        alt Validation Fails
            rect rgb(255, 230, 200)
                Note over GSA: Error Handling
                GSA->>Logger: Log violation
                GSA->>User: Reply with error message
                GSA->>Telegram: Schedule message deletion<br/>(2 min delay)
            end
        else Validation Passes
            GSA->>Bot: Continue processing
        end
    else Access Check Failed
        GSA->>Bot: Skip enforcement
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Areas requiring attention:

  • Validation logic in checkAlloggi, checkRipetizioni, and checkBooks methods to ensure hashtag enforcement is correctly implemented for each group type
  • Error message formatting and the 2-minute scheduled deletion mechanism to verify proper Telegram API usage and error handling
  • Admin/creator/role exclusion logic to confirm all intended exclusion criteria are properly applied
  • Integration point in src/bot.ts to ensure middleware registration order doesn't create conflicts with existing middleware

Poem

🐰 A filter so wise, now guards the crew,
Checking hashtags in groups, both old and new,
Admins and bots? They skip on by,
Rule-breakers warned, with messages to reply! 🎯
Two minutes pass, then—poof!—they fly.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'feat: group-specific actions middleware' directly summarizes the main change: adding a new GroupSpecificActions middleware to the codebase.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch group-specific

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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

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

♻️ Duplicate comments (1)
src/middlewares/group-specific-actions.ts (1)

24-30: Guard against undefined ctx.from to prevent runtime errors.

ctx.from can be undefined for messages sent on behalf of a channel (sender_chat) or anonymous group admins. Accessing ctx.from.id without a guard will throw a TypeError.

Add a guard at the start of the message handler:

      .on("message", async (ctx, next) => {
+        if (!ctx.from) return next() // skip channel posts / anonymous messages
        if (ctx.from.id === ctx.me.id) return next() // skip if bot
        const { roles } = await api.tg.permissions.getRoles.query({ userId: ctx.from.id })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 797c7f8 and 765a7e8.

📒 Files selected for processing (4)
  • README.md (1 hunks)
  • TODO.md (1 hunks)
  • src/bot.ts (2 hunks)
  • src/middlewares/group-specific-actions.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/middlewares/group-specific-actions.ts (6)
src/utils/types.ts (1)
  • Context (18-18)
src/backend.ts (1)
  • api (10-10)
src/logger.ts (1)
  • logger (3-8)
src/modules/index.ts (1)
  • modules (10-27)
src/utils/format.ts (2)
  • fmt (123-139)
  • fmtUser (141-144)
src/utils/wait.ts (1)
  • wait (11-17)
src/bot.ts (1)
src/middlewares/group-specific-actions.ts (1)
  • GroupSpecificActions (18-119)
🪛 GitHub Actions: Test
src/middlewares/group-specific-actions.ts

[error] 16-16: File content differs from formatting output. Formatting check failed (e.g., Prettier/formatter output mismatch).

🪛 markdownlint-cli2 (0.18.1)
TODO.md

32-32: Link text should be descriptive

(MD059, descriptive-link-text)

🔇 Additional comments (3)
README.md (1)

23-25: LGTM!

Helpful reference documentation for developers working on related messaging features.

src/bot.ts (1)

15-15: LGTM!

The middleware is correctly imported and registered in the appropriate position within the middleware stack.

Also applies to: 80-80

src/middlewares/group-specific-actions.ts (1)

71-114: Implementation looks reasonable for the check methods.

The hashtag validation logic correctly uses case-insensitive matching and returns descriptive error messages via the Result type.

Note: The code duplication across the three check methods was previously discussed and deferred to the reviewer (toto04). If you decide to refactor, consider extracting a shared helper like checkHashtags(ctx, allowedTags, errorMessage).

books: -1001164044303,
} as const

const TARGET_GROUP_IDS_SET = new Set(Object.values(TARGET_GROUPS));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix formatting: remove trailing semicolon.

The pipeline is failing due to a formatting mismatch. The project style doesn't use semicolons.

-const TARGET_GROUP_IDS_SET = new Set(Object.values(TARGET_GROUPS));
+const TARGET_GROUP_IDS_SET = new Set(Object.values(TARGET_GROUPS))
📝 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
const TARGET_GROUP_IDS_SET = new Set(Object.values(TARGET_GROUPS));
const TARGET_GROUP_IDS_SET = new Set(Object.values(TARGET_GROUPS))
🧰 Tools
🪛 GitHub Actions: Test

[error] 16-16: File content differs from formatting output. Formatting check failed (e.g., Prettier/formatter output mismatch).

🤖 Prompt for AI Agents
In src/middlewares/group-specific-actions.ts around line 16, remove the trailing
semicolon from the line initializing TARGET_GROUP_IDS_SET (currently "const
TARGET_GROUP_IDS_SET = new Set(Object.values(TARGET_GROUPS));") so it matches
the project's no-semicolon formatting style; update the line to end without a
semicolon and ensure file passes linter/formatter.

- [ ] do not delete Direttivo's allowed messages
- [x] check if user has username
- [ ] group-specific moderation (eg. #cerco #vendo in polihouse) see [here](https://github.com/PoliNetworkOrg/PoliNetworkBot_CSharp/blob/03c7434f06323ffdec301cb105d1d3b2c1ed4a95/PoliNetworkBot_CSharp/Code/Bots/Moderation/Blacklist/Blacklist.cs#L84)
- [x] group-specific moderation (eg. #cerco #vendo in polihouse) see [here](https://github.com/PoliNetworkOrg/PoliNetworkBot_CSharp/blob/03c7434f06323ffdec301cb105d1d3b2c1ed4a95/PoliNetworkBot_CSharp/Code/Bots/Moderation/Blacklist/Blacklist.cs#L84)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use descriptive link text instead of "here".

Per static analysis (MD059), the link text "here" is not accessible or descriptive. Consider something like "reference implementation" or "C# bot implementation".

-  - [x] group-specific moderation (eg. #cerco #vendo in polihouse) see [here](https://github.com/PoliNetworkOrg/PoliNetworkBot_CSharp/blob/03c7434f06323ffdec301cb105d1d3b2c1ed4a95/PoliNetworkBot_CSharp/Code/Bots/Moderation/Blacklist/Blacklist.cs#L84)
+  - [x] group-specific moderation (eg. #cerco #vendo in polihouse) see [reference implementation](https://github.com/PoliNetworkOrg/PoliNetworkBot_CSharp/blob/03c7434f06323ffdec301cb105d1d3b2c1ed4a95/PoliNetworkBot_CSharp/Code/Bots/Moderation/Blacklist/Blacklist.cs#L84)
📝 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
- [x] group-specific moderation (eg. #cerco #vendo in polihouse) see [here](https://github.com/PoliNetworkOrg/PoliNetworkBot_CSharp/blob/03c7434f06323ffdec301cb105d1d3b2c1ed4a95/PoliNetworkBot_CSharp/Code/Bots/Moderation/Blacklist/Blacklist.cs#L84)
- [x] group-specific moderation (eg. #cerco #vendo in polihouse) see [reference implementation](https://github.com/PoliNetworkOrg/PoliNetworkBot_CSharp/blob/03c7434f06323ffdec301cb105d1d3b2c1ed4a95/PoliNetworkBot_CSharp/Code/Bots/Moderation/Blacklist/Blacklist.cs#L84)
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

32-32: Link text should be descriptive

(MD059, descriptive-link-text)

🤖 Prompt for AI Agents
In TODO.md around line 32, the checklist item contains a markdown link using the
text "here", which is non-descriptive; replace the link text with a descriptive
phrase like "reference implementation" or "C# bot implementation" (e.g., change
"[here](URL)" to "[C# bot implementation](URL)") so the link text conveys
destination and resolves MD059.

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