Skip to content

feat!: support LID and PN JIDs in is_on_whatsapp - #453

Merged
jlucaso1 merged 5 commits into
mainfrom
feat/is-on-whatsapp-lid-support
Mar 28, 2026
Merged

feat!: support LID and PN JIDs in is_on_whatsapp#453
jlucaso1 merged 5 commits into
mainfrom
feat/is-on-whatsapp-lid-support

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Mar 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • is_on_whatsapp now takes &[Jid] instead of &[&str], supporting both PN (Jid::pn("123")) and LID (Jid::lid("100000001")) queries
  • Splits PN and LID into separate requests with correct query protocols per type, matching WA Web's ExistsJob exactly:
    • PN: <contact/>, <lid/>, <business><verified_name/></business>
    • LID: <lid/>, <business><verified_name/></business>
  • Parses pn_jid response attribute for LID-primary responses
  • Parses <result> node for per-protocol errors before <list> (WA Web parity)
  • Pre-populates known LIDs from cache on PN queries (optimization matching WA Web)
  • Persists LID-PN mappings from results in both directions
  • IsOnWhatsAppResult now includes lid, pn_jid, and is_business fields, marked #[non_exhaustive] for future-proofing
  • Validates JID types — skips non-PN/non-LID JIDs (groups, newsletters) with a warning
  • Removes get_info(), ContactInfo, and ContactInfoSpec (zero callers; status/picture_id available via get_user_info)

Breaking Changes

  • is_on_whatsapp(&[&str])is_on_whatsapp(&[Jid])
  • IsOnWhatsAppResult has new fields: lid, pn_jid, is_business
  • IsOnWhatsAppResult is now #[non_exhaustive]
  • ContactInfo type removed
  • get_info() method removed

Migration

// Before
let results = client.contacts().is_on_whatsapp(&["1234567890"]).await?;

// After
let results = client.contacts().is_on_whatsapp(&[Jid::pn("1234567890")]).await?;
// results[0].lid, results[0].pn_jid, results[0].is_business now available

// LID queries also supported
let results = client.contacts().is_on_whatsapp(&[Jid::lid("100000001")]).await?;

Test plan

  • cargo clippy --all --tests (zero warnings)
  • cargo test --all --lib (all pass)
  • New tests: LID build, known_lid pre-population, pn_jid parsing, LID query protocols, PN query protocols
  • Verified against WA Web captured JS: ExistsJob.js, Usync.js, Contact.js, Business.js, Lid.js

Summary by CodeRabbit

  • Refactor
    • Updated contact verification queries to use improved identifier format.
    • Enhanced contact registration detection with more accurate results.
    • Removed deprecated contact information structures from the public API.

BREAKING CHANGE: `is_on_whatsapp` now takes `&[Jid]` instead of `&[&str]`.
`ContactInfo`, `ContactInfoSpec`, and `get_info()` are removed.

Redesigns the IsOnWhatsApp usync query to match WA Web's ExistsJob:
- Queries `<contact/>`, `<lid/>`, and `<business/>` protocols
- Accepts both PN JIDs and LID JIDs (builds different user nodes per type)
- Parses `pn_jid` attribute from response user nodes
- Pre-populates known LIDs from cache on PN queries
- Persists LID-PN mappings from results in both directions
- IsOnWhatsAppResult now includes `lid`, `pn_jid`, and `is_business`
@coderabbitai

coderabbitai Bot commented Mar 28, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a0786b91-b6e2-4082-add2-f3e82a1ae2fc

📥 Commits

Reviewing files that changed from the base of the PR and between 0ebaba5 and 3c3b479.

📒 Files selected for processing (2)
  • src/features/contacts.rs
  • wacore/src/iq/usync.rs

📝 Walkthrough

Walkthrough

is_on_whatsapp now accepts Jid inputs, builds per-JID IsOnWhatsAppUser entries (with known_lid hints), issues separate PN and LID IsOnWhatsApp queries, merges IsOnWhatsAppResult vectors (now including pn_jid), persists LID↔PN mappings in two passes, and removes ContactInfo from exports.

Changes

Cohort / File(s) Summary
Contacts API
src/features/contacts.rs
Public method changed to is_on_whatsapp(&self, jids: &[Jid]). Builds IsOnWhatsAppUser items (injecting known_lid for PN JIDs), splits PN vs LID batches, issues up to two IsOnWhatsAppSpec queries, concatenates results, and persists LID↔PN mappings in two passes. Removed ContactInfo usage and related tests.
Feature re-exports
src/features/mod.rs, src/lib.rs
Removed ContactInfo from public re-exports; adjusted formatting/layout of re-export lists in lib.rs.
Core IQ / usync
wacore/src/iq/usync.rs
Reworked IsOnWhatsAppSpec to accept users: Vec<IsOnWhatsAppUser> plus query_type: IsOnWhatsAppQueryType. Request XML now differs for PN (uses <contact> and optional <lid jid="..."> hint) vs LID (<user jid="...@lid">). IsOnWhatsAppResult adds pn_jid: Option<Jid> and protocol-aware registration/business parsing. Removed ContactInfoSpec / ContactInfo structs and updated tests to PN/LID wire shapes.
Tests
wacore/src/iq/...tests, src/features/...tests
Unit tests updated to validate PN vs LID IQ shapes, known-LID hint injection, pn_jid parsing, and new registration inference; removed ContactInfo-specific tests while keeping ProfilePicture tests.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant Contacts as Contacts
  participant LidCache as LidCache
  participant WACoreIQ as WACoreIQ
  participant Server as Server
  participant Persist as Persist

  Client->>Contacts: is_on_whatsapp(jids: &[Jid])
  Contacts->>LidCache: lookup known_lid for PN JIDs
  LidCache-->>Contacts: known_lid? (Option<String>)
  Contacts->>WACoreIQ: send IsOnWhatsAppSpec(users, query_type=Pn)
  Contacts->>WACoreIQ: send IsOnWhatsAppSpec(users, query_type=Lid) (if any)
  WACoreIQ->>Server: IQ (PN: <contact>[+<lid>], LID: <user jid="...@lid">)
  Server-->>WACoreIQ: IQ response (users with lid / pn_jid / contact nodes)
  WACoreIQ-->>Contacts: Vec<IsOnWhatsAppResult> (includes pn_jid)
  Contacts->>Persist: persist_lid_mappings(from results)
  Contacts->>Persist: persist_pn_mappings(from pn_jid/lid pairs)
  Persist-->>Contacts: ack
  Contacts-->>Client: Vec<IsOnWhatsAppResult>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐇
I hop through JIDs with a whiskered cheer,
I peek for LIDs the cache keeps near,
I stitch PN and LID with a tiny map,
Persist the pairs, then take a nap,
Hooray — contacts linked, carrot clap!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding support for LID and PN JIDs in the is_on_whatsapp method, which is the primary objective across all modified files.
Docstring Coverage ✅ Passed Docstring coverage is 81.82% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/is-on-whatsapp-lid-support

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@jlucaso1

Copy link
Copy Markdown
Collaborator Author

@claude review

@claude

claude Bot commented Mar 28, 2026

Copy link
Copy Markdown

Claude finished @jlucaso1's task in 1m 23s —— View job


PR Review: feat!: support LID and PN JIDs in is_on_whatsapp

  • Read changed files
  • Analyze code quality and correctness
  • Post review feedback

Overall this is a clean, well-structured PR. The WA Web ExistsJob alignment is solid, tests are thorough, and the breaking change is well-justified. A few observations:

Minor Issues

1. Duplicated parsing logic in IsOnWhatsAppSpec::parse_responsewacore/src/iq/usync.rs:262-300

parse_user_common_fields already extracts jid, lid, and is_business from user nodes, but IsOnWhatsAppSpec::parse_response re-implements this inline. The only extras it needs are pn_jid and the LID-specific is_registered logic.

Consider reusing parse_user_common_fields and adding pn_jid parsing on top, which would reduce duplication and keep parsing consistent if the common logic ever changes. That said, this is minor since the is_registered logic for IsOnWhatsApp differs from UserInfo (LID presence-implies-registered vs contact-type check), so keeping them separate is also defensible.

2. parse_user_common_fields parses status but IsOnWhatsAppResult doesn't use itwacore/src/iq/usync.rs:131-165

After this PR, parse_user_common_fields still parses status and is_business, but it's only called by UserInfoSpec::parse_response. The status field is parsed even for the IsOnWhatsApp path if the helper were reused. Not a bug, just a note that this helper is now only used in one place — could simplify it or leave as-is for future use.

3. .unwrap() on pn_jid in contacts.rs:90src/features/contacts.rs:90

.map(|r| (r.pn_jid.as_ref().unwrap(), Some(&r.jid))),

This is safe because the preceding .filter() ensures r.pn_jid.is_some(), but it relies on the filter and map being read together. A filter_map with a match/destructure would make the safety self-evident:

.filter_map(|r| {
    if r.jid.is_lid() {
        r.pn_jid.as_ref().map(|pn| (pn, Some(&r.jid)))
    } else {
        None
    }
})

Fix this →

Looks Good

  • LID query wire format — using jid attribute for LID users vs <contact> child for PN users matches WA Web behavior correctly.
  • known_lid pre-population from cache is a nice optimization, correctly gated behind jid.is_pn().
  • LID-implies-registered logic (usync.rs:284) — if jid.is_lid() && contact_node.is_none() { true } correctly mirrors ExistsJob.js:70.
  • Bidirectional LID-PN persistence — both PN→LID (from results) and LID→PN (from pn_jid attribute) are persisted, with correct guards.
  • Clean removal of ContactInfo, ContactInfoSpec, and get_info() — no orphaned references.
  • Test coverage is good: LID build, known_lid pre-population, pn_jid parsing, not-registered case, phone formatting.
  • Breaking change documentation in the PR description is clear with migration examples.

Nit

  • PollOptionResult in features/mod.rs:44 export — unrelated to this PR, just noticed it's exported but not in lib.rs. Not something to address here.

LGTM with the optional filter_map cleanup. Nice work aligning with WA Web's ExistsJob.


@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8990ad59cb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread wacore/src/iq/usync.rs Outdated
phone.clone()
.map(|user| {
if user.jid.is_pn() {
let phone = format!("+{}", user.jid.user);

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 Badge Avoid double '+' in PN usync contact payloads

build_user_nodes now always prefixes PN users with '+', so inputs like Jid::pn("+1234567890") (or parsed JIDs whose user part already contains +) are serialized as ++1234567890. The previous implementation explicitly handled already-prefixed numbers, so this change can produce malformed <contact> values and false negatives from is_on_whatsapp for callers that preserve E.164 formatting.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/features/contacts.rs`:
- Around line 86-92: Replace the filter + map + unwrap pattern by using
filter_map so you don't call unwrap(): in the call to persist_lid_mappings,
change the iterator chain on results.iter() to a single filter_map closure that
checks r.jid.is_lid() and then uses r.pn_jid.as_ref().map(...) to return
Some((pn, Some(&r.jid))) only when pn_jid is present; keep the target function
name persist_lid_mappings and the same tuple shape so the call site and types
remain unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 420b49f9-0886-4f15-9d0a-985abb54d254

📥 Commits

Reviewing files that changed from the base of the PR and between f249d7d and 8990ad5.

📒 Files selected for processing (4)
  • src/features/contacts.rs
  • src/features/mod.rs
  • src/lib.rs
  • wacore/src/iq/usync.rs

Comment thread src/features/contacts.rs Outdated
@github-actions

github-actions Bot commented Mar 28, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Branchfeat/is-on-whatsapp-lid-support
Testbedubuntu-latest

🚨 1 Alert

BenchmarkMeasure
Units
ViewBenchmark Result
(Result Δ%)
Upper Boundary
(Limit %)
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions()Instructions
instructions x 1e3
📈 plot
🚷 threshold
🚨 alert (🔔)
47.13 x 1e3
(+9.50%)Baseline: 43.04 x 1e3
45.19 x 1e3
(104.28%)

Click to view all benchmark results
BenchmarkInstructionsBenchmark Result
instructions
(Result Δ%)
Upper Boundary
instructions
(Limit %)
binary_benchmark::attr_parser_group::bench_attr_parser attr_lookup:setup_attr_marshaled()📈 view plot
🚷 view threshold
6,197.00
(-5.76%)Baseline: 6,575.45
6,904.22
(89.76%)
binary_benchmark::child_iteration_group::bench_get_children_by_tag📈 view plot
🚷 view threshold
524,304.00
(-27.64%)Baseline: 724,578.39
760,807.31
(68.91%)
binary_benchmark::jid_optimization_group::bench_jid_to_owned_access jid_access:setup_jid_heavy_marshaled()📈 view plot
🚷 view threshold
20,868.00
(-5.82%)Baseline: 22,157.52
23,265.40
(89.70%)
binary_benchmark::marshal_group::bench_marshal_allocating📈 view plot
🚷 view threshold
98,202.00
(-17.59%)Baseline: 119,157.62
125,115.50
(78.49%)
binary_benchmark::marshal_group::bench_marshal_auto_allocating📈 view plot
🚷 view threshold
98,230.00
(-10.41%)Baseline: 109,646.46
115,128.79
(85.32%)
binary_benchmark::marshal_group::bench_marshal_auto_huge_bytes_allocating📈 view plot
🚷 view threshold
532,948.00
(-0.11%)Baseline: 533,542.76
560,219.89
(95.13%)
binary_benchmark::marshal_group::bench_marshal_auto_long_string📈 view plot
🚷 view threshold
15,870.00
(-4.86%)Baseline: 16,680.18
17,514.19
(90.61%)
binary_benchmark::marshal_group::bench_marshal_auto_many_children_allocating📈 view plot
🚷 view threshold
14,715,201.00
(-8.23%)Baseline: 16,034,200.66
16,835,910.69
(87.40%)
binary_benchmark::marshal_group::bench_marshal_exact_allocating📈 view plot
🚷 view threshold
118,358.00
(-21.12%)Baseline: 150,038.81
157,540.75
(75.13%)
binary_benchmark::marshal_group::bench_marshal_exact_huge_bytes_allocating📈 view plot
🚷 view threshold
534,378.00
(-0.11%)Baseline: 534,963.33
561,711.50
(95.13%)
binary_benchmark::marshal_group::bench_marshal_exact_long_string📈 view plot
🚷 view threshold
17,919.00
(-4.34%)Baseline: 18,731.55
19,668.13
(91.11%)
binary_benchmark::marshal_group::bench_marshal_exact_many_children_allocating📈 view plot
🚷 view threshold
28,066,347.00
(-22.16%)Baseline: 36,056,193.86
37,859,003.55
(74.13%)
binary_benchmark::marshal_group::bench_marshal_huge_bytes_allocating📈 view plot
🚷 view threshold
533,387.00
(-0.11%)Baseline: 533,981.76
560,680.84
(95.13%)
binary_benchmark::marshal_group::bench_marshal_long_string📈 view plot
🚷 view threshold
15,843.00
(-7.56%)Baseline: 17,138.78
17,995.72
(88.04%)
binary_benchmark::marshal_group::bench_marshal_many_children_allocating📈 view plot
🚷 view threshold
14,716,627.00
(-8.22%)Baseline: 16,035,312.01
16,837,077.61
(87.41%)
binary_benchmark::marshal_group::bench_marshal_reusing_buffer📈 view plot
🚷 view threshold
107,945.00
(-14.00%)Baseline: 125,518.44
131,794.37
(81.90%)
binary_benchmark::marshal_group::bench_marshal_reusing_buffer_vec_writer📈 view plot
🚷 view threshold
98,302.00
(-10.41%)Baseline: 109,718.46
115,204.39
(85.33%)
binary_benchmark::roundtrip_group::bench_roundtrip large:setup_large_marshaled()📈 view plot
🚷 view threshold
90,974.00
(-5.48%)Baseline: 96,246.27
101,058.58
(90.02%)
binary_benchmark::roundtrip_group::bench_roundtrip small:setup_small_marshaled()📈 view plot
🚷 view threshold
7,378.00
(-3.53%)Baseline: 7,647.83
8,030.22
(91.88%)
binary_benchmark::roundtrip_group::bench_roundtrip_auto large:setup_large_marshaled()📈 view plot
🚷 view threshold
91,005.00
(-1.85%)Baseline: 92,718.91
97,354.86
(93.48%)
binary_benchmark::roundtrip_group::bench_roundtrip_auto small:setup_small_marshaled()📈 view plot
🚷 view threshold
7,401.00
(+0.37%)Baseline: 7,373.47
7,742.14
(95.59%)
binary_benchmark::roundtrip_group::bench_roundtrip_exact large:setup_large_marshaled()📈 view plot
🚷 view threshold
106,790.00
(-1.58%)Baseline: 108,503.91
113,929.11
(93.73%)
binary_benchmark::roundtrip_group::bench_roundtrip_exact small:setup_small_marshaled()📈 view plot
🚷 view threshold
8,913.00
(+0.31%)Baseline: 8,885.47
9,329.74
(95.53%)
binary_benchmark::unmarshal_group::bench_unmarshal large:setup_large_marshaled()📈 view plot
🚷 view threshold
41,989.00
(-8.25%)Baseline: 45,766.09
48,054.39
(87.38%)
binary_benchmark::unmarshal_group::bench_unmarshal small:setup_small_marshaled()📈 view plot
🚷 view threshold
2,717.00
(-4.62%)Baseline: 2,848.55
2,990.97
(90.84%)
binary_benchmark::unpack_group::bench_unpack_compressed📈 view plot
🚷 view threshold
556,092.00
(+2.60%)Baseline: 542,008.87
569,109.31
(97.71%)
binary_benchmark::unpack_group::bench_unpack_uncompressed📈 view plot
🚷 view threshold
771.00
(-0.32%)Baseline: 773.45
812.12
(94.94%)
libsignal_benchmark::conversation_group::bench_full_dm_conversation full:setup_conversation_data()📈 view plot
🚷 view threshold
27,778,904.00
(+0.28%)Baseline: 27,702,003.22
29,087,103.38
(95.50%)
libsignal_benchmark::dm_group::bench_dm_decrypt_first_message decrypt_prekey:setup_dm_with_first_message()📈 view plot
🚷 view threshold
5,544,828.00
(-0.06%)Baseline: 5,547,971.64
5,825,370.22
(95.18%)
libsignal_benchmark::dm_group::bench_dm_encrypt_first_message first_msg:setup_dm_session()📈 view plot
🚷 view threshold
175,061.00
(-1.39%)Baseline: 177,521.59
186,397.67
(93.92%)
libsignal_benchmark::dm_group::bench_dm_encrypt_subsequent_message subsequent:setup_established_dm_session()📈 view plot
🚷 view threshold
175,710.00
(-1.45%)Baseline: 178,303.16
187,218.31
(93.85%)
libsignal_benchmark::dm_group::bench_dm_session_establishment setup:setup_dm_users()📈 view plot
🚷 view threshold
17,418,517.00
(+0.80%)Baseline: 17,280,407.76
18,144,428.15
(96.00%)
libsignal_benchmark::group_messaging_group::bench_group_create_distribution_message create:setup_group_sender()📈 view plot
🚷 view threshold
298,417.00
(+0.58%)Baseline: 296,704.24
311,539.45
(95.79%)
libsignal_benchmark::group_messaging_group::bench_group_decrypt_message decrypt:setup_group_with_encrypted_message()📈 view plot
🚷 view threshold
12,656,677.00
(+0.50%)Baseline: 12,593,499.47
13,223,174.44
(95.72%)
libsignal_benchmark::group_messaging_group::bench_group_encrypt_message encrypt:setup_group_with_distribution()📈 view plot
🚷 view threshold
719,543.00
(+0.39%)Baseline: 716,756.06
752,593.87
(95.61%)
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions()📈 view plot
🚷 view threshold
🚨 view alert (🔔)
47,129.00
(+9.50%)Baseline: 43,042.08
45,194.18
(104.28%)

libsignal_benchmark::session_optimization_group::bench_message_key_eviction eviction:setup_message_key_eviction()📈 view plot
🚷 view threshold
15,561,842.00
(+0.00%)Baseline: 15,561,760.78
16,339,848.81
(95.24%)
libsignal_benchmark::session_optimization_group::bench_out_of_order_decryption out_of_order:setup_out_of_order_messages()📈 view plot
🚷 view threshold
5,378,778.00
(-1.91%)Baseline: 5,483,458.70
5,757,631.63
(93.42%)
libsignal_benchmark::session_optimization_group::bench_promote_matching_session promote:setup_promote_matching_session()📈 view plot
🚷 view threshold
312,188.00
(-62.50%)Baseline: 832,490.92
874,115.46
(35.71%)
libsignal_benchmark::signature_group::bench_key_generation keygen📈 view plot
🚷 view threshold
2,830,547.00
(+0.19%)Baseline: 2,825,248.38
2,966,510.80
(95.42%)
libsignal_benchmark::signature_group::bench_signature_creation sign:setup_keypair_with_message()📈 view plot
🚷 view threshold
3,452,844.00
(-0.55%)Baseline: 3,471,990.80
3,645,590.34
(94.71%)
libsignal_benchmark::signature_group::bench_signature_verification verify:setup_keypair_with_message()📈 view plot
🚷 view threshold
125,040,888.00
(-0.24%)Baseline: 125,339,565.49
131,606,543.76
(95.01%)
reporting_token_benchmark::content_extraction_group::bench_content_extraction extended:setup_extended_message()📈 view plot
🚷 view threshold
12,002.00
(+1.59%)Baseline: 11,814.56
12,405.29
(96.75%)
reporting_token_benchmark::content_extraction_group::bench_content_extraction simple:setup_simple_message()📈 view plot
🚷 view threshold
3,930.00
(+2.58%)Baseline: 3,831.13
4,022.68
(97.70%)
reporting_token_benchmark::full_generation_group::bench_full_token_generation extended:setup_full_gen_extended()📈 view plot
🚷 view threshold
87,073.00
(-0.83%)Baseline: 87,800.29
92,190.31
(94.45%)
reporting_token_benchmark::full_generation_group::bench_full_token_generation simple:setup_full_gen_simple()📈 view plot
🚷 view threshold
79,022.00
(-1.02%)Baseline: 79,839.18
83,831.14
(94.26%)
reporting_token_benchmark::key_derivation_group::bench_key_derivation📈 view plot
🚷 view threshold
50,375.00
(-1.11%)Baseline: 50,942.88
53,490.02
(94.18%)
reporting_token_benchmark::message_encoding_group::bench_message_encoding extended:setup_extended_message()📈 view plot
🚷 view threshold
5,956.00
(+3.32%)Baseline: 5,764.73
6,052.97
(98.40%)
reporting_token_benchmark::message_encoding_group::bench_message_encoding simple:setup_simple_message()📈 view plot
🚷 view threshold
2,227.00
(+4.73%)Baseline: 2,126.48
2,232.81
(99.74%)
reporting_token_benchmark::token_calculation_group::bench_token_calculation📈 view plot
🚷 view threshold
21,920.00
(+0.01%)Baseline: 21,916.90
23,012.74
(95.25%)
🐰 View full continuous benchmarking report in Bencher

- Use filter_map instead of filter + map + unwrap for LID-PN persist
- Guard against double '+' prefix in PN user phone formatting

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wacore/src/iq/usync.rs`:
- Around line 91-95: The public IsOnWhatsAppUser shape currently allows invalid
combinations; either change it to a typed enum (e.g. enum IsOnWhatsAppUser { Pn
{ jid: Jid, known_lid: Option<String> }, Lid { jid: Jid } }) so callers can only
construct the two valid variants, or add strict validation in
IsOnWhatsAppSpec::new to fail fast: allow only (a) a PN-type JID with optional
known_lid (and if known_lid is Some validate it equals the raw LID user part
format) or (b) a LID-type JID with known_lid == None; return an Err or panic on
any other combination. Ensure validation logic references IsOnWhatsAppUser and
IsOnWhatsAppSpec::new so unsupported usync payloads cannot be constructed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e8acafd1-038d-46e3-a4fd-86370e952b6b

📥 Commits

Reviewing files that changed from the base of the PR and between 8990ad5 and 37fdea3.

📒 Files selected for processing (2)
  • src/features/contacts.rs
  • wacore/src/iq/usync.rs

Comment thread wacore/src/iq/usync.rs
- Split PN and LID queries into separate requests with correct protocols:
  PN queries: <contact/>, <lid/>, <business><verified_name/></business>
  LID queries: <business><verified_name/></business> only
- Add <verified_name/> child to <business/> query element
- Parse <result> node for per-protocol errors before <list>
@jlucaso1

Copy link
Copy Markdown
Collaborator Author

@claude review

@claude

claude Bot commented Mar 28, 2026

Copy link
Copy Markdown

Claude finished @jlucaso1's task in 1m 20s —— View job


PR Review: feat!: support LID and PN JIDs in is_on_whatsapp

  • Read changed files
  • Analyze code quality and correctness
  • Post review feedback

Well-structured PR that correctly aligns is_on_whatsapp with WA Web's ExistsJob behavior. The PN/LID split, per-type query protocols, bidirectional LID-PN persistence, and response parsing all look solid. Previous review feedback (filter_map cleanup, typed query validation) has been addressed in follow-up commits.

Remaining observations

1. parse_user_common_fields is now only used by UserInfoSpecwacore/src/iq/usync.rs:135-169

IsOnWhatsAppSpec::parse_response (line 303) does its own inline parsing rather than reusing parse_user_common_fields, which means there are now two independent jid/lid/is_business parsing paths. This is defensible since the is_registered logic differs (LID-implies-registered vs contact-type check), but worth noting: if common field parsing ever changes (e.g., a new lid attribute format), both paths need updating. Consider extracting just the jid+lid+is_business parsing into a shared helper, or adding a comment noting the intentional divergence.

2. ParsedUserFields::status is parsed but unused in the IsOnWhatsApp pathwacore/src/iq/usync.rs:131

The status field in ParsedUserFields is only consumed by UserInfoSpec. Since parse_user_common_fields is now only called from UserInfoSpec, this isn't a bug — just dead weight if it were ever shared. No action needed.

3. Benchmark alert is unrelated — The Bencher alert on bench_decrypt_with_previous_session (+9.5%) is in libsignal session optimization code, not in any file touched by this PR. Likely CI noise.

4. PN wire format doc comment vs actual query protocolswacore/src/iq/usync.rs:27-38

The doc comment XML example for "LID query" at lines 27-38 shows <contact/> and <lid/> in the <query> block, but the actual LID query (per IsOnWhatsAppQueryType::Lid at line 276-279) only emits <business>. The doc comment should be updated to match the actual wire format for LID queries — currently it shows the PN query format for both examples.

Fix this →

Looks Good

  • PN/LID request splitting in contacts.rs:69-98 — clean separation with correct IsOnWhatsAppQueryType per batch
  • known_lid pre-population from cache (contacts.rs:73) — nice WA Web parity optimization
  • build_user_nodes (usync.rs:98-124) — correctly handles + prefix for PN, jid attribute for LID, optional <lid> hint child
  • LID-implies-registered logic (usync.rs:338-339) — correctly mirrors ExistsJob.js:70
  • check_usync_result_errors (usync.rs:249-269) — good addition for WA Web parity
  • Bidirectional persistence (contacts.rs:100-109) — both PN→LID from results and LID→PN from pn_jid attribute, with the filter_map cleanup applied
  • Clean removal of ContactInfo/ContactInfoSpec/get_info() — no orphaned references
  • Test coverage — good: PN build, LID build, known_lid, pn_jid parsing, not-registered, phone formatting

LGTM with the optional doc comment fix. Nice work.


View job

Future fields (username, disappearing_mode, etc.) can be added
without a breaking API change.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/features/contacts.rs`:
- Around line 71-84: The loop currently treats any non-PN JID as a LID JID;
change the else branch to explicitly check jid.is_lid() and only push to
lid_users when true, otherwise log or skip unexpected JID types (e.g., warning
about group/newsletter JIDs) to make the contract explicit; use the existing
IsOnWhatsAppUser struct, the
self.client.lid_pn_cache.get_current_lid(&jid.user).await for PN handling, and
add a concise warning (e.g., warn!) referencing jid when an unexpected type is
encountered.

In `@wacore/src/iq/usync.rs`:
- Around line 336-344: The code that computes is_registered uses
contact_node.map(|c| c.attrs.get("type").is_some_and(|v| v == "in")) which is
inconsistent with the file's attrs().optional_string(...) pattern; update the
closure for contact_node to use c.attrs().optional_string("type").map_or(false,
|v| v == "in") (or equivalent) so parsing uses the same attrs().optional_string
API as elsewhere (ref: user_node, contact_node, is_registered).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 13518437-9538-4246-a157-6d922f77bb06

📥 Commits

Reviewing files that changed from the base of the PR and between 37fdea3 and 693017c.

📒 Files selected for processing (2)
  • src/features/contacts.rs
  • wacore/src/iq/usync.rs

Comment thread src/features/contacts.rs
Comment thread wacore/src/iq/usync.rs
Comment on lines +336 to +344
let contact_node = user_node.get_optional_child("contact");
// LID queries omit contact protocol; presence in response implies registered (ExistsJob.js:70)
let is_registered = if jid.is_lid() && contact_node.is_none() {
true
} else {
contact_node
.map(|c| c.attrs.get("type").is_some_and(|v| v == "in"))
.unwrap_or(false)
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Minor inconsistency in attribute access pattern.

Line 342 uses .attrs.get("type") directly while other parsing code in this file uses the attrs().optional_string() pattern (e.g., lines 317, 325-327, 332). This inconsistency doesn't affect functionality but reduces code uniformity.

♻️ Suggested consistency fix
             let is_registered = if jid.is_lid() && contact_node.is_none() {
                 true
             } else {
                 contact_node
-                    .map(|c| c.attrs.get("type").is_some_and(|v| v == "in"))
+                    .map(|c| c.attrs().optional_string("type").is_some_and(|v| v == "in"))
                     .unwrap_or(false)
             };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wacore/src/iq/usync.rs` around lines 336 - 344, The code that computes
is_registered uses contact_node.map(|c| c.attrs.get("type").is_some_and(|v| v ==
"in")) which is inconsistent with the file's attrs().optional_string(...)
pattern; update the closure for contact_node to use
c.attrs().optional_string("type").map_or(false, |v| v == "in") (or equivalent)
so parsing uses the same attrs().optional_string API as elsewhere (ref:
user_node, contact_node, is_registered).

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0ebaba5f25

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/features/contacts.rs Outdated
Comment on lines +78 to +82
} else {
lid_users.push(IsOnWhatsAppUser {
jid: jid.to_non_ad(),
known_lid: None,
});

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 Badge Reject non-user JIDs before adding them to LID queries

is_on_whatsapp is documented to accept only PN and LID JIDs, but this branch treats every non-PN JID as a LID lookup candidate. That means inputs like group/newsletter/status JIDs get serialized into the LID usync request, which can produce malformed lookups and (in mixed batches) cause the whole call to error after a successful PN request. Adding an explicit jid.is_lid() check (and rejecting unsupported servers) would avoid false negatives and hard failures from accidental non-user inputs.

Useful? React with 👍 / 👎.

- Include <lid/> protocol in LID queries (WA Web adds it unconditionally)
- Skip non-PN/non-LID JIDs with warning instead of silently treating
  them as LID queries
- Extract parse_lid_jid() to deduplicate lid parsing across specs
- Fix doc comments to match actual wire format
@jlucaso1
jlucaso1 merged commit 7514429 into main Mar 28, 2026
7 checks passed
@jlucaso1
jlucaso1 deleted the feat/is-on-whatsapp-lid-support branch March 28, 2026 15:44

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3c3b479b77

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/features/contacts.rs
if !pn_users.is_empty() {
let sid = self.client.generate_request_id();
let spec = IsOnWhatsAppSpec::new(pn_users, sid, IsOnWhatsAppQueryType::Pn);
results.extend(self.client.execute(spec).await?);

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 Badge Preserve caller order when combining PN and LID results

is_on_whatsapp builds two independent batches and appends PN responses first, then LID responses, so mixed inputs can come back in a different order than requested (for example, [LID, PN] becomes [PN, LID]). Because this API returns a Vec, callers often associate entries by index with the original slice; reordering here can silently misattribute registration/business data to the wrong JID in mixed queries.

Useful? React with 👍 / 👎.

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