Skip to content

Fix: Admin Revoke (Delete messages from others)#7

Open
moothz wants to merge 2 commits intoEvolutionAPI:mainfrom
moothz:feature/fix-delete-message
Open

Fix: Admin Revoke (Delete messages from others)#7
moothz wants to merge 2 commits intoEvolutionAPI:mainfrom
moothz:feature/fix-delete-message

Conversation

@moothz
Copy link
Copy Markdown

@moothz moothz commented Apr 4, 2026

Fixes the limitation where group admins were unable to delete messages sent by other participants via the API. This enables the "admin revoke" functionality.

These changes have been in effect in my codebase since december (early acess) - throughly tested.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Performance improvement

Testing

  • Manual testing completed
  • Functionality verified in development environment
  • No breaking changes introduced

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have tested my changes thoroughly
  • Any dependent changes have been merged and published

Summary by Sourcery

Enable revoking messages sent by other participants in group chats via the message delete API.

Bug Fixes:

  • Allow group admins to delete messages sent by other users by correctly passing the original sender JID when revoking messages.

Enhancements:

  • Extend message delete payload to support specifying whether the message is from the caller and, when needed, the original participant JID.

Documentation:

  • Update API message deletion docs with new fromMe and participant fields, clarify admin revoke behavior and time limits, and add examples for deleting own and others' messages.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Apr 4, 2026

Reviewer's Guide

Enables group admins to revoke messages sent by other participants via the API by extending the delete-message payload with sender context and wiring it into the WhatsApp client revoke call, and updates the API documentation with the new parameters and examples.

Sequence diagram for admin revoke (delete message from another participant)

sequenceDiagram
    actor AdminUser
    participant ApiServer
    participant MessageService
    participant Utils
    participant WhatsAppClient

    AdminUser->>ApiServer: POST /message/delete {chat, messageId, fromMe=false, participant}
    ApiServer->>MessageService: DeleteMessageEveryone(data, instance)
    MessageService->>MessageService: Parse chat JID (recipient)
    MessageService->>Utils: ParseJID(participant)
    Utils-->>MessageService: senderJID or error
    alt invalid participant JID
        MessageService-->>ApiServer: error "invalid participant JID"
        ApiServer-->>AdminUser: HTTP 4xx
    else valid participant JID
        MessageService->>WhatsAppClient: SendMessage(recipient, BuildRevoke(recipient, senderJID, messageId))
        WhatsAppClient-->>MessageService: revoke response
        MessageService-->>ApiServer: success payload
        ApiServer-->>AdminUser: HTTP 200
    end
Loading

Class diagram for updated message deletion payload (MessageStruct)

classDiagram
    class MessageStruct {
        +string Chat
        +string MessageID
        +bool FromMe
        +string Participant
    }

    class MessageService {
        +DeleteMessageEveryone(data MessageStruct, instance Instance)
    }

    MessageService --> MessageStruct
Loading

File-Level Changes

Change Details Files
Extend delete-message API payload to carry sender context for admin revokes.
  • Add FromMe and Participant fields to MessageStruct used by delete-everyone endpoint
  • Make Participant optional at JSON level but required at runtime when deleting messages not sent by the caller
pkg/message/service/message_service.go
Implement server-side logic to support admin revocation of other users' messages.
  • Derive senderJID based on FromMe flag, defaulting to EmptyJID for own messages
  • Validate that Participant is present and a valid JID when FromMe is false, with clear error messages and logging
  • Pass computed senderJID into client.BuildRevoke instead of always using EmptyJID
pkg/message/service/message_service.go
Document the new admin revoke behavior and parameters in the API guide.
  • Update delete-everyone endpoint request body to show chat group JID, fromMe flag, and participant JID
  • Document fromMe and participant fields, including when participant becomes mandatory
  • Clarify behavioral notes around admin revoke vs own-message deletion and update time-limit description
  • Add separate cURL examples for revoking own messages and admin revoking others' messages
docs/wiki/guias-api/api-messages.md

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

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

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 security issue, 2 other issues, and left some high level feedback:

Security issues:

  • Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource. (link)

General comments:

  • The new FromMe field in MessageStruct defaults to false when omitted, which contradicts the documented default of true and will break existing clients that don’t send fromMe; consider making it a pointer with a default of true or treating an absent fromMe as true in DeleteMessageEveryone.
  • In the ParseJID error path, the log call LogError("[%s] Error parsing participant JID for non-FromMe message: %s", data.Participant) has two %s placeholders but only one argument and appears to log participant where instance.Id is expected; update the arguments/placeholders to include both values correctly.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `FromMe` field in `MessageStruct` defaults to `false` when omitted, which contradicts the documented default of `true` and will break existing clients that don’t send `fromMe`; consider making it a pointer with a default of `true` or treating an absent `fromMe` as `true` in `DeleteMessageEveryone`.
- In the `ParseJID` error path, the log call `LogError("[%s] Error parsing participant JID for non-FromMe message: %s", data.Participant)` has two `%s` placeholders but only one argument and appears to log `participant` where `instance.Id` is expected; update the arguments/placeholders to include both values correctly.

## Individual Comments

### Comment 1
<location path="pkg/message/service/message_service.go" line_range="388-389" />
<code_context>
+		if data.Participant == "" {
+			return "", "", errors.New("participant is required to delete a message from another user")
+		}
+		parsedJID, ok := utils.ParseJID(data.Participant)
+		if !ok {
+			m.loggerWrapper.GetLogger(instance.Id).LogError("[%s] Error parsing participant JID for non-FromMe message: %s", data.Participant)
+			return "", "", errors.New("invalid participant JID")
</code_context>
<issue_to_address>
**issue (bug_risk):** LogError format string expects two placeholders but only one argument is provided

In the `!ok` branch, the `LogError` format string has two `%s` placeholders but only `data.Participant` is passed. This can cause incorrect logging or a runtime panic, depending on the logger. Please either pass two arguments (e.g. `instance.Id` and `data.Participant`) or update the format string to match the single argument.
</issue_to_address>

### Comment 2
<location path="pkg/message/service/message_service.go" line_range="382-385" />
<code_context>
 	}

+	var senderJID types.JID
+	if data.FromMe {
+		senderJID = types.EmptyJID
+	} else {
+		if data.Participant == "" {
+			return "", "", errors.New("participant is required to delete a message from another user")
+		}
</code_context>
<issue_to_address>
**question (bug_risk):** New FromMe/participant requirement may break existing clients that don’t send these fields

Previously, delete-everyone calls always used `types.EmptyJID` and did not require `FromMe` or `Participant`. With this change, requests that omit `FromMe` will be treated as non-FromMe and will now fail with "participant is required..." if `Participant` is empty, potentially breaking existing integrations. If you need backward compatibility, consider a fallback where `FromMe` is treated as true when `Participant` is empty, or gate the new behavior via versioning/feature flag.
</issue_to_address>

### Comment 3
<location path="docs/wiki/guias-api/api-messages.md" line_range="941-943" />
<code_context>
SUA-CHAVE-API
</code_context>
<issue_to_address>
**security (curl-auth-header):** Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.

*Source: gitleaks*
</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.

Comment on lines +388 to +389
parsedJID, ok := utils.ParseJID(data.Participant)
if !ok {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): LogError format string expects two placeholders but only one argument is provided

In the !ok branch, the LogError format string has two %s placeholders but only data.Participant is passed. This can cause incorrect logging or a runtime panic, depending on the logger. Please either pass two arguments (e.g. instance.Id and data.Participant) or update the format string to match the single argument.

Comment on lines +382 to +385
if data.FromMe {
senderJID = types.EmptyJID
} else {
if data.Participant == "" {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

question (bug_risk): New FromMe/participant requirement may break existing clients that don’t send these fields

Previously, delete-everyone calls always used types.EmptyJID and did not require FromMe or Participant. With this change, requests that omit FromMe will be treated as non-FromMe and will now fail with "participant is required..." if Participant is empty, potentially breaking existing integrations. If you need backward compatibility, consider a fallback where FromMe is treated as true when Participant is empty, or gate the new behavior via versioning/feature flag.

Comment on lines +941 to +943
curl -X POST http://localhost:4000/message/delete \
-H "Content-Type: application/json" \
-H "apikey: SUA-CHAVE-API" \
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security (curl-auth-header): Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.

Source: gitleaks

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