-
Notifications
You must be signed in to change notification settings - Fork 0
feat: group-specific actions middleware #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this 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.
| 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() |
Copilot
AI
Oct 17, 2025
There was a problem hiding this comment.
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.
| 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." | |
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Up to @toto04
Co-authored-by: Copilot <[email protected]>
There was a problem hiding this 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.
| 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 |
Copilot
AI
Oct 17, 2025
There was a problem hiding this comment.
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.
| !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." |
Copilot
AI
Oct 17, 2025
There was a problem hiding this comment.
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.
| "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." |
| const TARGET_GROUPS: Record<string, number> = { | ||
| alloggi: -1001175999519, | ||
| ripetizioni: -1001495422899, | ||
| books: -1001164044303, | ||
| } as const |
Copilot
AI
Oct 17, 2025
There was a problem hiding this comment.
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.
| 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() | ||
| } |
Copilot
AI
Oct 17, 2025
There was a problem hiding this comment.
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.
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
|
Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughThis 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Areas requiring attention:
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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.
Example instruction:
Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later. Comment |
There was a problem hiding this 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 undefinedctx.fromto prevent runtime errors.
ctx.fromcan beundefinedfor messages sent on behalf of a channel (sender_chat) or anonymous group admins. Accessingctx.from.idwithout a guard will throw aTypeError.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
📒 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
Resulttype.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)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| - [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.
Summary by CodeRabbit
Documentation
New Features
✏️ Tip: You can customize this high-level summary in your review settings.