Skip to content

feat(whatsapp): allow processing messages from self and groups#42

Open
winterborn wants to merge 1 commit intotechieanant:mainfrom
winterborn:feature/process-from-self-and-groups
Open

feat(whatsapp): allow processing messages from self and groups#42
winterborn wants to merge 1 commit intotechieanant:mainfrom
winterborn:feature/process-from-self-and-groups

Conversation

@winterborn
Copy link

Description

Adds opt-in options to process messages from yourself and from groups, in addition to the default 1:1 chats from others. Users can enable these in a new Message sources section on the WhatsApp Connection page (toggles save immediately). Default behaviour is unchanged: only 1:1 chats from others are processed unless both options are enabled.

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
  • Code refactoring
  • Performance improvement
  • Test addition/improvement
  • Dependency update
  • Other (please describe):

Changes Made

Backend

  • Added processFromSelf and processGroups to whatsapp_connections (schema + migration 0013); both default false.
  • WhatsApp client: only skips fromMe when !processFromSelf, only skips groups when !processGroups (reads options from connection).
  • Message handler: for group messages, uses participant (and participantAlt) for sender identity; replies still go to the group JID.
  • Filter API: PUT /api/whatsapp/filter accepts optional processFromSelf and processGroups; status and filter responses include them.
  • Repository, model, and settings export updated for the new fields; unit tests updated.

Frontend

  • New Message sources card (separate from Message Filtering) with two Switch toggles: “Process messages from myself” and “Process group messages”.
  • Toggles save immediately (same filter API with current filter + new source flags).
  • Toast “Message sources updated” shows appended list, e.g. “1:1 from others, from self, from groups.”.
  • Message Filtering form no longer contains message-source controls; filter payload is only filterType / filterValue (page still sends current sources when saving filter).

Testing

How Has This Been Tested?

  • Unit tests
  • Integration tests
  • E2E tests
  • Manual testing

Test Configuration

  • Node.js version: 20+
  • npm version: 10+
  • OS: Mac / Windows

Test Coverage

Backend unit tests updated/passing (whatsapp controller, whatsapp-connection repository). No new coverage metric reported.

  • Coverage before: X%
  • Coverage after: X%

Checklist

Code Quality

  • My code follows the project's coding conventions
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have run npm run lint and resolved all issues
  • I have run npm run format to format my code
  • My changes generate no new warnings

Testing

  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published

Documentation

  • I have updated the documentation accordingly
  • I have updated the README.md if needed
  • I have added/updated JSDoc comments for new functions
  • I have updated the API documentation if applicable

Dependencies

  • I have checked for and resolved any dependency conflicts
  • I have run npm audit to check for security vulnerabilities
  • I have updated the changelog if this is a significant change

Commits

  • My commits follow the conventional commits specification
  • I have squashed any unnecessary commits
  • My commit messages are clear and descriptive

Breaking Changes

None

Migration guide:

  • Run migration 0013_add_process_from_self_and_groups.sql (or npm run db:migrate) so the new columns exist.

Additional Notes

  • UI for message sources is shown only when WhatsApp is connected (same as Message Filtering).

For Reviewers

None.

Questions for Reviewers

None.


- Add processFromSelf and processGroups options to WhatsApp connection
- Default remains 1:1 from others only; opt-in via Message Filtering UI
- For groups, use participant JID for sender identity and reply in group
- Schema migration 0013; API filter endpoint accepts optional booleans
- UI: Message sources section with checkboxes on WhatsApp Connection page
Copy link

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 opt-in controls to process additional WhatsApp message sources (messages sent by the linked account and group chats) while keeping the default behavior unchanged (only 1:1 messages from others).

Changes:

  • Added processFromSelf / processGroups flags to WhatsApp connection persistence (schema + migration) and exposed them via status/filter APIs.
  • Updated WhatsApp client + message handler to allow self/group processing and to derive sender identity from participant for group messages.
  • Added a new “Message sources” UI card with immediate-save toggles and adjusted filter form payload shape.

Reviewed changes

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

Show a summary per file
File Description
frontend/src/types/whatsapp.types.ts Extends connection + filter config types with source flags.
frontend/src/pages/whatsapp-connection.tsx Adds MessageSourcesCard and updates save handlers to include source flags.
frontend/src/hooks/use-whatsapp.ts Ensures default query data includes the new flags.
frontend/src/components/whatsapp/message-sources-card.tsx New UI card with two immediate-save toggles.
frontend/src/components/whatsapp/message-filter-form.tsx Refactors onSave to use a payload object.
backend/src/db/schema.ts Adds process_from_self and process_groups columns (boolean-mode ints).
backend/drizzle/migrations/0013_add_process_from_self_and_groups.sql Migration to add the two new columns with defaults.
backend/src/models/whatsapp-connection.model.ts Adds the two flags to model + API response types.
backend/src/api/validators/whatsapp.validators.ts Allows optional processFromSelf / processGroups in filter payload.
backend/src/api/controllers/whatsapp.controller.ts Returns flags in status + filter update response; persists flags via repository.
backend/src/repositories/whatsapp-connection.repository.ts Persists flags in updateMessageFilter and maps them from DB rows.
backend/src/services/whatsapp/whatsapp-client.service.ts Applies flags when deciding whether to forward incoming messages.
backend/src/services/whatsapp/message-handler.service.ts Uses participant-based sender identity for group messages.
backend/src/api/controllers/settings.controller.ts Includes the new flags in exported settings data.
backend/tests/unit/controllers/whatsapp.controller.test.ts Updates unit test expectations for new flags/options argument.
backend/tests/unit/repositories/whatsapp-connection.repository.test.ts Updates repository unit test fixtures/expectations for new flags.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +313 to +315
if (fromMe && !processFromSelf) continue;
if ((isGroup || isBroadcast) && !processGroups) continue;
if (!remoteJid) continue;
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

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

The new gating treats status@broadcast as a “group” message (isBroadcast) and will start processing broadcast/status messages when processGroups is enabled. That’s likely unintended (the feature is “process group messages”), and could result in noisy/incorrect processing. Suggest always skipping status@broadcast regardless of processGroups, or introducing a separate explicit flag for broadcasts.

Suggested change
if (fromMe && !processFromSelf) continue;
if ((isGroup || isBroadcast) && !processGroups) continue;
if (!remoteJid) continue;
if (!remoteJid) continue;
if (isBroadcast) continue;
if (fromMe && !processFromSelf) continue;
if (isGroup && !processGroups) continue;

Copilot uses AI. Check for mistakes.
Comment on lines +158 to +159
const senderJid = isGroup ? (message.key.participant ?? message.key.remoteJid) : null;

Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

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

For group messages, senderJid falls back to message.key.remoteJid when participant is missing. That makes the sender identity resolve to the group JID, causing all such messages to share the same userIdentifier/hash/session (and participantAlt selection also becomes inconsistent). Prefer using only participant for sender identity (leave it null/undefined if missing) and handle the missing-participant case explicitly (e.g., skip processing or log and return).

Suggested change
const senderJid = isGroup ? (message.key.participant ?? message.key.remoteJid) : null;
const senderJid = isGroup ? message.key.participant : null;
// If this is a group message but we don't have a participant, we cannot safely
// determine the sender identity. Log and skip processing to avoid conflating
// all group senders under the group JID.
if (isGroup && !senderJid) {
logger.warn(
{
rawJid: message.key.remoteJid,
remoteJidAlt: message.key.remoteJidAlt,
participant: message.key.participant,
participantAlt: message.key.participantAlt,
fromMe: message.key.fromMe,
id: message.key.id,
},
'Skipping group message without participant; cannot determine sender identity safely'
);
return;
}

Copilot uses AI. Check for mistakes.
Comment on lines +153 to +157
{
...payload,
processFromSelf: status?.processFromSelf ?? false,
processGroups: status?.processGroups ?? false,
},
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

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

handleFilterSave always sends processFromSelf/processGroups using status?.… ?? false. If the page is considered connected via realtimeConnected before the full /status payload (with persisted flags) has been refetched, saving a filter can unintentionally reset the message source flags to false. Consider disabling the save until status is loaded, or sending undefined for these fields when status hasn’t been fetched yet so the backend leaves them unchanged.

Copilot uses AI. Check for mistakes.
Comment on lines +179 to +182
updateFilter(
{
filterType: status?.filterType ?? null,
filterValue: status?.filterValue ?? null,
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

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

handleMessageSourcesSave derives filterType/filterValue from status?.… ?? null. If the UI is connected via realtimeConnected but status hasn’t been refetched yet, toggling sources will send null filter fields and can clear an existing filter unintentionally. Consider guarding on status being present (or using the last known filter values from query data) before allowing source updates.

Suggested change
updateFilter(
{
filterType: status?.filterType ?? null,
filterValue: status?.filterValue ?? null,
if (!status) {
toast({
title: 'Unable to update message sources',
description: 'Connection status is not yet loaded. Please try again in a moment.',
variant: 'destructive',
});
return;
}
updateFilter(
{
filterType: status.filterType,
filterValue: status.filterValue,

Copilot uses AI. Check for mistakes.
Comment on lines +302 to 306
const connections = await whatsappConnectionRepository.findAll();
const conn = connections[0];
const processFromSelf = conn?.processFromSelf ?? false;
const processGroups = conn?.processGroups ?? false;

Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

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

messages.upsert now queries the DB (whatsappConnectionRepository.findAll()) on every event to read processFromSelf/processGroups. This can become a hot-path bottleneck under message load. Consider caching these flags in memory (refresh on filter update) or at least using getActive()/a limit 1 query instead of findAll().

Copilot uses AI. Check for mistakes.
Copy link
Owner

@techieanant techieanant left a comment

Choose a reason for hiding this comment

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

Please see review comments.

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.

3 participants