Skip to content

fix(contacts): resolve LID↔PN for profile-picture contacts.update (port PR #2605)#506

Merged
rsalcara merged 1 commit into
developfrom
feat/pr2605-contact-picture-identity
Jun 6, 2026
Merged

fix(contacts): resolve LID↔PN for profile-picture contacts.update (port PR #2605)#506
rsalcara merged 1 commit into
developfrom
feat/pr2605-contact-picture-identity

Conversation

@rsalcara

@rsalcara rsalcara commented Jun 6, 2026

Copy link
Copy Markdown
Owner

Summary

Port upstream WhiskeySockets/Baileys#2605 (resolveContactPictureIdentity) so the profile-picture contacts.update event carries both LID and phoneNumber when available — consumers can correlate the change with caches keyed by either addressing form.

  • New helper src/Utils/contact-picture-identity.ts (49 lines)
  • Refactored case 'picture': in src/Socket/messages-recv.ts:
    • Splits group vs individual dispatch explicitly
    • Individual path calls resolveContactPictureIdentity → emits { id, lid?, phoneNumber? }
    • Group path emits { id: from } matching WA Android single-jid pattern
  • Customization preserved: the .attrs.hash fallback when notification.attrs.from is empty (defensive — upstream removes it; we keep)
  • getPNForLID bound at socket top scope (line 157), alongside existing getLIDForPN

Empirical validation (Frida — WA Android Business 2.26.21.75)

Captured via SQLite Frida hook on com.whatsapp.w4b paired companion. Full report: memory/wa_android_profile_picture_lid_resolution.md.

Scenario WA Android pattern New emit shape
Individual + LID known UPDATE wa_contacts WHERE jid IN (LID, PN) — both rows { id: PN, lid: LID, phoneNumber: PN }
Group UPDATE wa_contacts WHERE jid IN (@g.us) — single jid { id: @g.us }
Newsletter (@newsletter, type=21) UPDATE wa_contacts WHERE jid IN (@newsletter) + INSERT newsletter { id: @newsletter } (else-if branch C)
Emoji/Sticker as avatar Identical to photo (server rasterizes) imgUrl='changed' (transparent)
Hosted LID (not testable) isLidUser strict → skip lookup → { id }

The PR WhiskeySockets#2605 design mirrors WA Android's internal behavior — WA maintains two wa_contacts rows for LID-resolved contacts and updates them atomically. Emitting both forms lets Baileys consumers do the same.

Test plan

  • npm run build passes (3 phases, zero errors)
  • 8/8 new unit tests pass — npm test -- contact-picture-identity
  • Full test suite: 41 pre-existing failures unrelated (better-sqlite3 native bindings on Windows — same failures observed on develop without these changes; zero failing test imports the modified code)
  • Manual smoke after merge: run Baileys consumer, ask a LID-resolved contact to change their photo, verify contacts.update[0] has { id, lid, phoneNumber, imgUrl: 'changed' }

Risks / mitigations

Risk Mitigation
Consumer doing strict-equality on update.id Additive fields (lid, phoneNumber) don't break existing field checks
authState.creds.me undefined during reconnect Function handles meId: undefinedisBogusSelf guard becomes falsy, no crash
Conflict with PR #503 (already merged) None — #503 only touched newsletter.ts; no overlap

What's NOT in this PR (orthogonal discoveries from same capture session)

The Frida capture also discovered:

  • status.db (14th SQLite database) — not in wa_android_sqlite_master_reference. State machine (0/1/3/6), status_attribution.type=1 for channel crossposts. Documented in memory/wa_android_newsletter_admin_invite_status_share.md. Not added to Phase 9 multi-DB because Baileys has no share-to-status outbound caller today — would be dead code.
  • message_newsletter_admin_invite schema — only stores {channel, expiration}, not invitee. Inbound handler exists upstream; outbound emission not in scope.
  • Sidebar open triggers ~35 picture refreshes — behavior of the WA app UI, not protocol; no fork action needed.

🤖 Generated with Claude Code


Summary by cubic

Fixes profile-picture contacts.update to include both LID and phone number for individual contacts, so consumers can match updates against caches keyed by either form. Group and newsletter updates remain unchanged.

  • Bug Fixes
    • Individual picture updates now emit { id, lid?, phoneNumber?, imgUrl } via resolveContactPictureIdentity.
    • Group/newsletter path emits { id: from, imgUrl } and preserves existing stub behavior.
    • Keeps .attrs.hash as a fallback when from is missing; binds getPNForLID at socket scope.
    • Adds src/Utils/contact-picture-identity.ts and exports from Utils; includes unit tests for resolution logic.

Written for commit 621aa12. Summary will update on new commits.

Review in cubic

…rt PR WhiskeySockets#2605)

Port upstream WhiskeySockets#2605 (resolveContactPictureIdentity) so the
profile-picture contacts.update event carries both LID and phoneNumber when
available, letting consumers correlate the change with caches keyed by either
addressing form.

Empirically validated against WA Android Business 2.26.21.75 via Frida SQLite
hook over 5 scenarios:
- Individual contact with known LID: WA updates BOTH wa_contacts rows
  (LID + PN) atomically via WHERE jid IN (?,?). The new emit shape
  ({id, lid, phoneNumber}) lets the consumer replicate that pattern.
- Group: single-jid update (WHERE jid IN (@g.us)). The branch
  isJidGroup(from) emits {id: from} matching the WA pattern.
- Newsletter (channel @newsletter, type=21): single-jid update. Falls through
  to the resolveContactPictureIdentity branch C ({id: from} only) — emoji/
  sticker avatars hit the same code path (server rasterizes them).
- PN-only: covered by unit tests #2 and #6 (not reproducible on-device because
  WA only monitors pictures of LID-resolved contacts).
- Bogus-self mapping: guarded by resolveContactPictureIdentity's bogus-self
  check, preventing a stranger's LID from polluting our own contact entry.

Customization preserved from this fork: the `.attrs.hash` fallback when
notification.attrs.from is empty stays as a defensive catch.

Adds 8-case unit suite covering: LID→PN resolve, fallback to raw LID, bogus
self guard, self-LID-with-self-PN preserved, PN→LID fill, non-LID PN-only,
swallowed resolver errors, and hosted-LID skip.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 6, 2026 20:41
@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e1f3f129-86a9-4d60-8362-59c5f30cfc19

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/pr2605-contact-picture-identity

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown

Thanks for opening this pull request and contributing to the project!

The next step is for the maintainers to review your changes. If everything looks good, it will be approved and merged into the main branch.

In the meantime, anyone in the community is encouraged to test this pull request and provide feedback.

✅ How to confirm it works

If you’ve tested this PR, please comment below with:

Tested and working ✅

This helps us speed up the review and merge process.

📦 To test this PR locally:

# NPM
npm install @whiskeysockets/baileys@rsalcara/InfiniteAPI#feat/pr2605-contact-picture-identity

# Yarn (v2+)
yarn add @whiskeysockets/baileys@rsalcara/InfiniteAPI#feat/pr2605-contact-picture-identity

# PNPM
pnpm add @whiskeysockets/baileys@rsalcara/InfiniteAPI#feat/pr2605-contact-picture-identity

If you encounter any issues or have feedback, feel free to comment as well.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Ports upstream Baileys behavior so contacts.update emitted from profile-picture (notification type='picture') events includes both addressing forms (LID and PN) when available, enabling consumers to correlate updates with caches keyed by either identity.

Changes:

  • Added resolveContactPictureIdentity helper to best-effort resolve LID↔PN and emit { id, lid?, phoneNumber? }.
  • Refactored messages-recv handling for case 'picture' to split group vs individual emission and attach resolved identity for individuals.
  • Added unit tests covering the identity-resolution behavior and edge cases (bogus self, hosted LIDs, resolver failures).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/Utils/index.ts Exports the new contact-picture identity helper via the Utils barrel.
src/Utils/contact-picture-identity.ts Implements LID↔PN resolution logic for picture notifications.
src/Socket/messages-recv.ts Uses the helper when emitting contacts.update for picture notifications; binds getPNForLID at socket scope.
src/tests/Utils/contact-picture-identity.test.ts Adds unit tests for the new identity resolution helper.

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

Comment on lines +2291 to 2295
const pictureNode = setPicture || delPicture
const pictureFrom = jidNormalizedUser(node?.attrs?.from) || pictureNode?.attrs?.hash
const pictureImgUrl = setPicture ? 'changed' : 'removed'

if (isJidGroup(from)) {

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 4 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/Socket/messages-recv.ts">

<violation number="1" location="src/Socket/messages-recv.ts:2311">
P2: When `node.attrs.from` is empty (which this code defends against via the `hash` fallback in `pictureFrom`), `from` will be `''` and `isJidGroup(from)` will be false. If the `hash` fallback contains a group JID, this `else if` branch would incorrectly handle a group picture update through the individual identity-resolution path. The branch condition should use `pictureFrom` (or `pictureFrom || from`) instead of `from` to ensure the hash fallback is still correctly classified as a group update.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

...(result.key || {}),
participant: setPicture?.attrs.author
}
} else if (pictureFrom) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: When node.attrs.from is empty (which this code defends against via the hash fallback in pictureFrom), from will be '' and isJidGroup(from) will be false. If the hash fallback contains a group JID, this else if branch would incorrectly handle a group picture update through the individual identity-resolution path. The branch condition should use pictureFrom (or pictureFrom || from) instead of from to ensure the hash fallback is still correctly classified as a group update.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/Socket/messages-recv.ts, line 2311:

<comment>When `node.attrs.from` is empty (which this code defends against via the `hash` fallback in `pictureFrom`), `from` will be `''` and `isJidGroup(from)` will be false. If the `hash` fallback contains a group JID, this `else if` branch would incorrectly handle a group picture update through the individual identity-resolution path. The branch condition should use `pictureFrom` (or `pictureFrom || from`) instead of `from` to ensure the hash fallback is still correctly classified as a group update.</comment>

<file context>
@@ -2283,37 +2285,41 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
 						...(result.key || {}),
 						participant: setPicture?.attrs.author
 					}
+				} else if (pictureFrom) {
+					const identity = await resolveContactPictureIdentity(pictureFrom, {
+						getPNForLID,
</file context>

@rsalcara rsalcara merged commit f335c1f into develop Jun 6, 2026
7 checks passed
rsalcara added a commit that referenced this pull request Jun 6, 2026
release: develop → master 2026-06-06 (#503 + #506 + #507)
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.

2 participants