Skip to content

feat(group): member-add-mode, join-approval-mode, participant requests + message forward#2595

Open
alpesdigital-adm wants to merge 1 commit into
evolution-foundation:mainfrom
alpesdigital-adm:feat/group-admin-controls-and-forward
Open

feat(group): member-add-mode, join-approval-mode, participant requests + message forward#2595
alpesdigital-adm wants to merge 1 commit into
evolution-foundation:mainfrom
alpesdigital-adm:feat/group-admin-controls-and-forward

Conversation

@alpesdigital-adm

@alpesdigital-adm alpesdigital-adm commented Jun 20, 2026

Copy link
Copy Markdown

What

Adds REST endpoints for group/message features that baileys (the underlying lib) already implements but Evolution doesn't expose yet:

Endpoint baileys function
POST /group/memberAddMode groupMemberAddMode — restrict who adds members (admin_add / all_member_add)
POST /group/joinApprovalMode groupJoinApprovalMode — turn membership approval on/off (on / off)
GET /group/participantRequests groupRequestParticipantsList — list pending join requests
POST /group/updateParticipantRequests groupRequestParticipantsUpdate — approve/reject requests
POST /message/forwardMessage sendMessage(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.9 in this repo) but with no HTTP surface until now.

How

Each endpoint follows the existing updateSetting / toggleEphemeral pattern: DTO + JSONSchema + router + controller + baileys service call. forwardMessage reuses the private getMessage(key, full) to load the stored WAMessage and passes it to sendMessage(jid, { forward }).

No new dependencies. tsc --noEmit + tsup build 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:

  • Add API support for forwarding previously stored WhatsApp messages to a target number.
  • Add API endpoints to configure group member add mode and join approval mode.
  • Add API endpoints to list and update pending group participant join requests.

Enhancements:

  • Extend validation schemas and DTOs to cover new group membership modes, participant request updates, and message forwarding payloads.

…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>
@sourcery-ai

sourcery-ai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds 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 message

sequenceDiagram
  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
Loading

Sequence diagram for group participant request management endpoints

sequenceDiagram
  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
Loading

File-Level Changes

Change Details Files
Expose group member-add restriction and join-approval toggles via new DTOs, schemas, controller methods, and service wrappers over Baileys client APIs.
  • Introduce GroupMemberAddModeDto and GroupJoinApprovalModeDto extending GroupJid with constrained mode fields
  • Define JSON Schemas memberAddModeSchema and joinApprovalModeSchema with enum validation and non-empty constraints
  • Add GroupController methods delegating updateMemberAddMode and updateJoinApprovalMode calls to per-instance BaileysStartupService
  • Implement BaileysStartupService.updateMemberAddMode and updateJoinApprovalMode to call client.groupMemberAddMode and client.groupJoinApprovalMode and wrap responses/errors
  • Register POST /group/memberAddMode and POST /group/joinApprovalMode routes in GroupRouter using existing groupValidate flow and new schemas
src/api/dto/group.dto.ts
src/validate/group.schema.ts
src/api/controllers/group.controller.ts
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
src/api/routes/group.router.ts
Add group participant-requests listing and approval/rejection endpoints wired through schema, DTO, controller, and Baileys client wrappers.
  • Introduce GroupUpdateParticipantRequestDto with action and participants fields extending GroupJid
  • Define updateParticipantRequestSchema with enum-constrained action and non-empty, unique participants array
  • Add GroupController methods findParticipantRequests and updateParticipantRequests delegating to service layer
  • Implement BaileysStartupService.findParticipantRequests and updateParticipantRequests using client.groupRequestParticipantsList and client.groupRequestParticipantsUpdate with JID normalization, returning structured result objects
  • Register GET /group/participantRequests and POST /group/updateParticipantRequests routes in GroupRouter using groupValidate and new schemas
src/api/dto/group.dto.ts
src/validate/group.schema.ts
src/api/controllers/group.controller.ts
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
src/api/routes/group.router.ts
Introduce a message forwarding endpoint that loads a stored message and forwards it via Baileys while preserving the forwarded tag.
  • Add ForwardMessageDto carrying number and messageId
  • Define forwardMessageSchema with number and messageId validation, reusing numberDefinition and isNotEmpty
  • Register POST /message/forwardMessage in MessageRouter using dataValidate and ForwardMessageDto
  • Add SendMessageController.forwardMessage to delegate to the per-instance service
  • Implement BaileysStartupService.forwardMessage to resolve the stored WAMessage via getMessage, validate presence of message payload, normalize number to WhatsApp JID, and call client.sendMessage with a forward payload, wrapping errors as BadRequestException
src/api/dto/sendMessage.dto.ts
src/validate/message.schema.ts
src/api/controllers/sendMessage.controller.ts
src/api/routes/sendMessage.router.ts
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts

Possibly linked issues

  • #member_add_mode.: PR implements groupMemberAddMode via POST /group/memberAddMode, fulfilling the issue’s request for member_add_mode control.
  • #unknown: The PR adds /message/forwardMessage, implementing the requested native endpoint to forward existing WhatsApp messages.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • 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.
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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
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`));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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:

  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.

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.

1 participant