From 8307758d7ec6d2d7840da4444c50768f304e2d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Sat, 4 Apr 2026 11:31:51 -0300 Subject: [PATCH 1/3] feat!: add `get_lid_from_phone_number` and change LID-PN API to accept `&Jid` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING: `get_phone_number_from_lid` now takes `&Jid` instead of `&str`. Add symmetric `get_lid_from_phone_number(&Jid)` for PN→LID lookup. Using `&Jid` avoids manual string parsing and allocations — the user part is already available as a field. --- src/client/lid_pn.rs | 80 ++++++++++++++++++++++++++++++++---------- src/features/groups.rs | 2 +- 2 files changed, 63 insertions(+), 19 deletions(-) diff --git a/src/client/lid_pn.rs b/src/client/lid_pn.rs index b3de0e9a8..73f4d868d 100644 --- a/src/client/lid_pn.rs +++ b/src/client/lid_pn.rs @@ -238,24 +238,14 @@ impl Client { } } - /// Get the phone number (user part) for a given LID. - /// Looks up the LID-PN mapping from the in-memory cache. - /// - /// # Arguments - /// - /// * `lid` - The LID user part (e.g., "100000012345678") or full JID (e.g., "100000012345678@lid") - /// - /// # Returns - /// - /// The phone number user part if a mapping exists, None otherwise. - pub async fn get_phone_number_from_lid(&self, lid: &str) -> Option { - // Handle both full JID (e.g., "100000012345678@lid") and user part only - let lid_user = if lid.contains('@') { - lid.split('@').next().unwrap_or(lid) - } else { - lid - }; - self.lid_pn_cache.get_phone_number(lid_user).await + /// Get the phone number for a given LID JID. + pub async fn get_phone_number_from_lid(&self, lid: &Jid) -> Option { + self.lid_pn_cache.get_phone_number(&lid.user).await + } + + /// Get the current LID for a given phone number JID. + pub async fn get_lid_from_phone_number(&self, phone: &Jid) -> Option { + self.lid_pn_cache.get_current_lid(&phone.user).await } } @@ -307,4 +297,58 @@ mod tests { assert_eq!(resolved, pn_jid); } + + #[tokio::test] + async fn test_get_lid_from_phone_number() { + let client: Arc = create_test_client().await; + let pn = "55999999999"; + let lid = "100000012345678"; + + assert!( + client + .get_lid_from_phone_number(&Jid::pn(pn)) + .await + .is_none() + ); + + client + .add_lid_pn_mapping(lid, pn, LearningSource::Usync) + .await + .unwrap(); + + assert_eq!( + client + .get_lid_from_phone_number(&Jid::pn(pn)) + .await + .as_deref(), + Some(lid), + ); + } + + #[tokio::test] + async fn test_get_phone_number_from_lid() { + let client: Arc = create_test_client().await; + let pn = "55999999999"; + let lid = "100000012345678"; + + assert!( + client + .get_phone_number_from_lid(&Jid::lid(lid)) + .await + .is_none() + ); + + client + .add_lid_pn_mapping(lid, pn, LearningSource::Usync) + .await + .unwrap(); + + assert_eq!( + client + .get_phone_number_from_lid(&Jid::lid(lid)) + .await + .as_deref(), + Some(pn), + ); + } } diff --git a/src/features/groups.rs b/src/features/groups.rs index f0e3a33d6..771916dfa 100644 --- a/src/features/groups.rs +++ b/src/features/groups.rs @@ -191,7 +191,7 @@ impl<'a> Groups<'a> { let resolved = if participant.jid.is_lid() && participant.phone_number.is_none() { let phone_number = self .client - .get_phone_number_from_lid(&participant.jid.user) + .get_phone_number_from_lid(&participant.jid) .await .ok_or_else(|| { anyhow::anyhow!("Missing phone number mapping for LID {}", participant.jid) From c530a52f82df3eaff41b2fd33f1f3290c5563fd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Sat, 4 Apr 2026 11:41:00 -0300 Subject: [PATCH 2/3] refactor!: consolidate LID-PN lookup into single `get_lid_pn_entry(&Jid)` Replace `get_phone_number_from_lid` and `get_lid_from_phone_number` with a single `get_lid_pn_entry` that routes by JID type and returns the full `LidPnEntry` (both lid and phone_number in one lookup). --- src/client/lid_pn.rs | 55 +++++++++++++++--------------------------- src/features/groups.rs | 6 ++--- 2 files changed, 22 insertions(+), 39 deletions(-) diff --git a/src/client/lid_pn.rs b/src/client/lid_pn.rs index 73f4d868d..92e7ad527 100644 --- a/src/client/lid_pn.rs +++ b/src/client/lid_pn.rs @@ -238,14 +238,15 @@ impl Client { } } - /// Get the phone number for a given LID JID. - pub async fn get_phone_number_from_lid(&self, lid: &Jid) -> Option { - self.lid_pn_cache.get_phone_number(&lid.user).await - } - - /// Get the current LID for a given phone number JID. - pub async fn get_lid_from_phone_number(&self, phone: &Jid) -> Option { - self.lid_pn_cache.get_current_lid(&phone.user).await + /// Look up the LID↔phone mapping for a JID. + /// + /// Routes automatically: LID JIDs search by LID, PN JIDs search by phone. + pub async fn get_lid_pn_entry(&self, jid: &Jid) -> Option { + if jid.is_lid() { + self.lid_pn_cache.get_entry_by_lid(&jid.user).await + } else { + self.lid_pn_cache.get_entry_by_phone(&jid.user).await + } } } @@ -299,56 +300,38 @@ mod tests { } #[tokio::test] - async fn test_get_lid_from_phone_number() { + async fn test_get_lid_pn_entry_from_pn() { let client: Arc = create_test_client().await; let pn = "55999999999"; let lid = "100000012345678"; - assert!( - client - .get_lid_from_phone_number(&Jid::pn(pn)) - .await - .is_none() - ); + assert!(client.get_lid_pn_entry(&Jid::pn(pn)).await.is_none()); client .add_lid_pn_mapping(lid, pn, LearningSource::Usync) .await .unwrap(); - assert_eq!( - client - .get_lid_from_phone_number(&Jid::pn(pn)) - .await - .as_deref(), - Some(lid), - ); + let entry = client.get_lid_pn_entry(&Jid::pn(pn)).await.unwrap(); + assert_eq!(entry.lid, lid); + assert_eq!(entry.phone_number, pn); } #[tokio::test] - async fn test_get_phone_number_from_lid() { + async fn test_get_lid_pn_entry_from_lid() { let client: Arc = create_test_client().await; let pn = "55999999999"; let lid = "100000012345678"; - assert!( - client - .get_phone_number_from_lid(&Jid::lid(lid)) - .await - .is_none() - ); + assert!(client.get_lid_pn_entry(&Jid::lid(lid)).await.is_none()); client .add_lid_pn_mapping(lid, pn, LearningSource::Usync) .await .unwrap(); - assert_eq!( - client - .get_phone_number_from_lid(&Jid::lid(lid)) - .await - .as_deref(), - Some(pn), - ); + let entry = client.get_lid_pn_entry(&Jid::lid(lid)).await.unwrap(); + assert_eq!(entry.lid, lid); + assert_eq!(entry.phone_number, pn); } } diff --git a/src/features/groups.rs b/src/features/groups.rs index 771916dfa..3e5f6ad23 100644 --- a/src/features/groups.rs +++ b/src/features/groups.rs @@ -189,14 +189,14 @@ impl<'a> Groups<'a> { for participant in options.participants { let resolved = if participant.jid.is_lid() && participant.phone_number.is_none() { - let phone_number = self + let entry = self .client - .get_phone_number_from_lid(&participant.jid) + .get_lid_pn_entry(&participant.jid) .await .ok_or_else(|| { anyhow::anyhow!("Missing phone number mapping for LID {}", participant.jid) })?; - participant.with_phone_number(Jid::pn(phone_number)) + participant.with_phone_number(Jid::pn(entry.phone_number)) } else { participant }; From 9a98c054b452f3135c7df112bb26fb47c3077e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Sat, 4 Apr 2026 11:59:19 -0300 Subject: [PATCH 3/3] fix: reject non-user JIDs in get_lid_pn_entry Group, newsletter, and broadcast JIDs should return None instead of hitting the phone-number cache with a non-phone user part. --- src/client/lid_pn.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/client/lid_pn.rs b/src/client/lid_pn.rs index 92e7ad527..0497bfef0 100644 --- a/src/client/lid_pn.rs +++ b/src/client/lid_pn.rs @@ -241,11 +241,14 @@ impl Client { /// Look up the LID↔phone mapping for a JID. /// /// Routes automatically: LID JIDs search by LID, PN JIDs search by phone. + /// Returns `None` for non-user JIDs (groups, newsletters, etc.). pub async fn get_lid_pn_entry(&self, jid: &Jid) -> Option { if jid.is_lid() { self.lid_pn_cache.get_entry_by_lid(&jid.user).await - } else { + } else if jid.is_pn() { self.lid_pn_cache.get_entry_by_phone(&jid.user).await + } else { + None } } }