feat(group): member-add-mode, join-approval-mode, participant requests + message forward#2595
Conversation
…s + message forward
Exposes Baileys group/message capabilities that already exist in the underlying
library but had no REST endpoints:
- POST /group/memberAddMode -> groupMemberAddMode (admin_add | all_member_add)
- POST /group/joinApprovalMode -> groupJoinApprovalMode (on | off)
- GET /group/participantRequests -> groupRequestParticipantsList (pending join requests)
- POST /group/updateParticipantRequests -> groupRequestParticipantsUpdate (approve | reject)
- POST /message/forwardMessage -> sendMessage(jid, { forward }) (keeps isForwarded)
Each follows the existing pattern (DTO + JSONSchema + router + controller +
baileys service). No new dependencies — baileys 7.0.0-rc.9 already ships these.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewer's GuideAdds REST endpoints and DTO/schema/controller/service plumbing to expose new WhatsApp group admin capabilities (member-add restrictions, join approval, participant request management) and a message-forwarding endpoint that reuses stored WAMessages and Baileys forwarding, following existing validation/router patterns. Sequence diagram for forwarding a stored WhatsApp messagesequenceDiagram
actor Client
participant MessageRouter
participant SendMessageController
participant BaileysStartupService
participant BaileysClient
Client->>MessageRouter: POST /message/forwardMessage
MessageRouter->>MessageRouter: dataValidate ForwardMessageDto
MessageRouter->>SendMessageController: forwardMessage(instance, data)
SendMessageController->>BaileysStartupService: forwardMessage(data)
BaileysStartupService->>BaileysStartupService: getMessage({ id: messageId }, true)
BaileysStartupService->>BaileysClient: sendMessage(jid, { forward: fullMsg })
BaileysClient-->>BaileysStartupService: sendMessage result
BaileysStartupService-->>SendMessageController: forward result
SendMessageController-->>MessageRouter: response
MessageRouter-->>Client: 201 Created
Sequence diagram for group participant request management endpointssequenceDiagram
actor Admin
participant GroupRouter
participant GroupController
participant BaileysStartupService
participant BaileysClient
rect rgb(235, 245, 255)
Admin->>GroupRouter: GET /group/participantRequests
GroupRouter->>GroupRouter: groupValidate GroupJid
GroupRouter->>GroupController: findParticipantRequests(instance, groupJid)
GroupController->>BaileysStartupService: findParticipantRequests(groupJid)
BaileysStartupService->>BaileysClient: groupRequestParticipantsList(groupJid)
BaileysClient-->>BaileysStartupService: requests
BaileysStartupService-->>GroupController: { requests }
GroupController-->>GroupRouter: { requests }
GroupRouter-->>Admin: 200 OK
end
rect rgb(235, 255, 235)
Admin->>GroupRouter: POST /group/updateParticipantRequests
GroupRouter->>GroupRouter: groupValidate GroupUpdateParticipantRequestDto
GroupRouter->>GroupController: updateParticipantRequests(instance, update)
GroupController->>BaileysStartupService: updateParticipantRequests(update)
BaileysStartupService->>BaileysClient: groupRequestParticipantsUpdate(groupJid, participants, action)
BaileysClient-->>BaileysStartupService: update result
BaileysStartupService-->>GroupController: { updateParticipantRequests: result }
GroupController-->>GroupRouter: response
GroupRouter-->>Admin: 201 Created
end
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In
forwardMessage, the innerthrow new BadRequestException('Message not found')is immediately caught and wrapped as a generic'Error forwarding message', so callers lose the specific error; consider either not catching that case or rethrowing it unchanged whenfullMsg?.messageis missing. - The logic to normalize phone numbers/JIDs (e.g.,
number.replace(/\D/g, '')and appending@s.whatsapp.netinforwardMessageandupdateParticipantRequests) appears ad hoc; consider extracting and reusing a shared helper to keep JID handling consistent across endpoints. - In
updateParticipantRequestSchema,participantsis required but not included in theisNotEmptycall, so empty strings would pass validation; it may be worth addingparticipantstoisNotEmptyor tightening the item schema if you want to reject blank participant entries.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `forwardMessage`, the inner `throw new BadRequestException('Message not found')` is immediately caught and wrapped as a generic `'Error forwarding message'`, so callers lose the specific error; consider either not catching that case or rethrowing it unchanged when `fullMsg?.message` is missing.
- The logic to normalize phone numbers/JIDs (e.g., `number.replace(/\D/g, '')` and appending `@s.whatsapp.net` in `forwardMessage` and `updateParticipantRequests`) appears ad hoc; consider extracting and reusing a shared helper to keep JID handling consistent across endpoints.
- In `updateParticipantRequestSchema`, `participants` is required but not included in the `isNotEmpty` call, so empty strings would pass validation; it may be worth adding `participants` to `isNotEmpty` or tightening the item schema if you want to reject blank participant entries.
## Individual Comments
### Comment 1
<location path="src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts" line_range="4647" />
<code_context>
+
+ public async updateParticipantRequests(update: GroupUpdateParticipantRequestDto) {
+ try {
+ const participants = update.participants.map((p) => (p.includes('@') ? p : `${p}@s.whatsapp.net`));
+ const result = await this.client.groupRequestParticipantsUpdate(update.groupJid, participants, update.action);
+ return { updateParticipantRequests: result };
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Participant JID normalization is inconsistent with number normalization elsewhere and may accept malformed identifiers.
Participants are treated as JIDs whenever they contain `@`, and otherwise `@s.whatsapp.net` is appended, but there’s no stripping/validation of the local part. If callers pass phone numbers with spaces, dashes, or other formatting, this will produce invalid JIDs. Please either reuse the same normalization helper used in `forwardMessage` (or similar) or enforce that inputs are already-normalized JIDs or strictly numeric IDs before appending the domain.
Suggested implementation:
```typescript
public async updateParticipantRequests(update: GroupUpdateParticipantRequestDto) {
try {
const normalizeParticipant = (raw: string): string => {
const value = raw.trim();
if (!value) {
throw new BadRequestException('Participant identifier cannot be empty');
}
// If it's already a JID, return as-is (after trimming)
if (value.includes('@')) {
return value;
}
// Treat as phone-like input: strip non-digits and validate
const digitsOnly = value.replace(/\D/g, '');
if (!digitsOnly) {
throw new BadRequestException(
`Invalid participant identifier "${raw}". Expected a JID or numeric phone number.`,
);
}
return `${digitsOnly}@s.whatsapp.net`;
};
const participants = update.participants.map(normalizeParticipant);
const result = await this.client.groupRequestParticipantsUpdate(update.groupJid, participants, update.action);
return { updateParticipantRequests: result };
} catch (error) {
throw new BadRequestException('Error updating participant requests', error.toString());
}
}
```
If your codebase already has a shared normalization helper (e.g. used in `forwardMessage`), consider:
1. Replacing the in-method `normalizeParticipant` function with a call to that shared helper for consistency.
2. If that helper returns bare numbers instead of full JIDs, adjust the logic to append `@s.whatsapp.net` only when necessary, keeping the validation/stripping behavior consistent.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
|
||
| public async updateParticipantRequests(update: GroupUpdateParticipantRequestDto) { | ||
| try { | ||
| const participants = update.participants.map((p) => (p.includes('@') ? p : `${p}@s.whatsapp.net`)); |
There was a problem hiding this comment.
suggestion (bug_risk): Participant JID normalization is inconsistent with number normalization elsewhere and may accept malformed identifiers.
Participants are treated as JIDs whenever they contain @, and otherwise @s.whatsapp.net is appended, but there’s no stripping/validation of the local part. If callers pass phone numbers with spaces, dashes, or other formatting, this will produce invalid JIDs. Please either reuse the same normalization helper used in forwardMessage (or similar) or enforce that inputs are already-normalized JIDs or strictly numeric IDs before appending the domain.
Suggested implementation:
public async updateParticipantRequests(update: GroupUpdateParticipantRequestDto) {
try {
const normalizeParticipant = (raw: string): string => {
const value = raw.trim();
if (!value) {
throw new BadRequestException('Participant identifier cannot be empty');
}
// If it's already a JID, return as-is (after trimming)
if (value.includes('@')) {
return value;
}
// Treat as phone-like input: strip non-digits and validate
const digitsOnly = value.replace(/\D/g, '');
if (!digitsOnly) {
throw new BadRequestException(
`Invalid participant identifier "${raw}". Expected a JID or numeric phone number.`,
);
}
return `${digitsOnly}@s.whatsapp.net`;
};
const participants = update.participants.map(normalizeParticipant);
const result = await this.client.groupRequestParticipantsUpdate(update.groupJid, participants, update.action);
return { updateParticipantRequests: result };
} catch (error) {
throw new BadRequestException('Error updating participant requests', error.toString());
}
}If your codebase already has a shared normalization helper (e.g. used in forwardMessage), consider:
- Replacing the in-method
normalizeParticipantfunction with a call to that shared helper for consistency. - If that helper returns bare numbers instead of full JIDs, adjust the logic to append
@s.whatsapp.netonly when necessary, keeping the validation/stripping behavior consistent.
What
Adds REST endpoints for group/message features that baileys (the underlying lib) already implements but Evolution doesn't expose yet:
POST /group/memberAddModegroupMemberAddMode— restrict who adds members (admin_add/all_member_add)POST /group/joinApprovalModegroupJoinApprovalMode— turn membership approval on/off (on/off)GET /group/participantRequestsgroupRequestParticipantsList— list pending join requestsPOST /group/updateParticipantRequestsgroupRequestParticipantsUpdate— approve/reject requestsPOST /message/forwardMessagesendMessage(jid, { forward })— forward a stored message (keeps the forwarded tag)Why
Common group-admin needs (membership approval, locking member-add) and message forwarding. All supported by
baileys(7.0.0-rc.9in this repo) but with no HTTP surface until now.How
Each endpoint follows the existing
updateSetting/toggleEphemeralpattern: DTO + JSONSchema + router + controller + baileys service call.forwardMessagereuses the privategetMessage(key, full)to load the storedWAMessageand passes it tosendMessage(jid, { forward }).No new dependencies.
tsc --noEmit+tsupbuild pass; pre-commit lint clean.🤖 Generated with Claude Code
Summary by Sourcery
Expose new WhatsApp group admin controls and message forwarding capabilities over the HTTP API.
New Features:
Enhancements: