fix(contacts): resolve LID↔PN for profile-picture contacts.update (port PR #2605)#506
Conversation
…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>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
|
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 worksIf you’ve tested this PR, please comment below with: This helps us speed up the review and merge process. 📦 To test this PR locally:If you encounter any issues or have feedback, feel free to comment as well. |
There was a problem hiding this comment.
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
resolveContactPictureIdentityhelper to best-effort resolve LID↔PN and emit{ id, lid?, phoneNumber? }. - Refactored
messages-recvhandling forcase '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.
| const pictureNode = setPicture || delPicture | ||
| const pictureFrom = jidNormalizedUser(node?.attrs?.from) || pictureNode?.attrs?.hash | ||
| const pictureImgUrl = setPicture ? 'changed' : 'removed' | ||
|
|
||
| if (isJidGroup(from)) { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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>
Summary
Port upstream WhiskeySockets/Baileys#2605 (
resolveContactPictureIdentity) so the profile-picturecontacts.updateevent carries both LID and phoneNumber when available — consumers can correlate the change with caches keyed by either addressing form.src/Utils/contact-picture-identity.ts(49 lines)case 'picture':insrc/Socket/messages-recv.ts:resolveContactPictureIdentity→ emits{ id, lid?, phoneNumber? }{ id: from }matching WA Android single-jid pattern.attrs.hashfallback whennotification.attrs.fromis empty (defensive — upstream removes it; we keep)getPNForLIDbound at socket top scope (line 157), alongside existinggetLIDForPNEmpirical 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.UPDATE wa_contacts WHERE jid IN (LID, PN)— both rows{ id: PN, lid: LID, phoneNumber: PN }UPDATE wa_contacts WHERE jid IN (@g.us)— single jid{ id: @g.us }@newsletter, type=21)UPDATE wa_contacts WHERE jid IN (@newsletter)+INSERT newsletter{ id: @newsletter }(else-if branch C)imgUrl='changed'(transparent)isLidUserstrict → skip lookup →{ id }The PR WhiskeySockets#2605 design mirrors WA Android's internal behavior — WA maintains two
wa_contactsrows for LID-resolved contacts and updates them atomically. Emitting both forms lets Baileys consumers do the same.Test plan
npm run buildpasses (3 phases, zero errors)npm test -- contact-picture-identitydevelopwithout these changes; zero failing test imports the modified code)contacts.update[0]has{ id, lid, phoneNumber, imgUrl: 'changed' }Risks / mitigations
update.idlid,phoneNumber) don't break existing field checksauthState.creds.meundefined during reconnectmeId: undefined—isBogusSelfguard becomes falsy, no crashnewsletter.ts; no overlapWhat's NOT in this PR (orthogonal discoveries from same capture session)
The Frida capture also discovered:
status.db(14th SQLite database) — not inwa_android_sqlite_master_reference. State machine (0/1/3/6),status_attribution.type=1for channel crossposts. Documented inmemory/wa_android_newsletter_admin_invite_status_share.md. Not added to Phase 9 multi-DB because Baileys has noshare-to-statusoutbound caller today — would be dead code.message_newsletter_admin_inviteschema — only stores{channel, expiration}, not invitee. Inbound handler exists upstream; outbound emission not in scope.🤖 Generated with Claude Code
Summary by cubic
Fixes profile-picture
contacts.updateto 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.{ id, lid?, phoneNumber?, imgUrl }viaresolveContactPictureIdentity.{ id: from, imgUrl }and preserves existing stub behavior..attrs.hashas a fallback whenfromis missing; bindsgetPNForLIDat socket scope.src/Utils/contact-picture-identity.tsand exports fromUtils; includes unit tests for resolution logic.Written for commit 621aa12. Summary will update on new commits.