1use std::borrow::Cow;
2use std::fmt;
3use std::str::FromStr;
4
5#[derive(Debug, Clone, Copy)]
8pub struct ParsedJidParts<'a> {
9 pub user: &'a str,
10 pub server: &'a str,
11 pub agent: u8,
12 pub device: u16,
13 pub integrator: u16,
14}
15
16#[inline]
22pub fn parse_jid_fast(s: &str) -> Option<ParsedJidParts<'_>> {
23 if s.is_empty() {
24 return None;
25 }
26
27 let bytes = s.as_bytes();
28
29 let mut at_pos: Option<usize> = None;
31 let mut colon_pos: Option<usize> = None;
32 let mut last_dot_pos: Option<usize> = None;
33
34 for (i, &b) in bytes.iter().enumerate() {
35 match b {
36 b'@' => {
37 if at_pos.is_none() {
38 at_pos = Some(i);
39 }
40 }
41 b':' => {
42 if at_pos.is_none() {
44 colon_pos = Some(i);
45 }
46 }
47 b'.' => {
48 if at_pos.is_none() && colon_pos.is_none() {
50 last_dot_pos = Some(i);
51 }
52 }
53 _ => {}
54 }
55 }
56
57 let at = match at_pos {
59 Some(pos) => pos,
60 None => {
61 return None;
63 }
64 };
65
66 let user_part = &s[..at];
67 let server = &s[at + 1..];
68
69 if user_part.is_empty() {
71 return None;
72 }
73
74 if server == HIDDEN_USER_SERVER {
76 let (user, device) = match colon_pos {
77 Some(pos) if pos < at => {
78 let device_slice = &s[pos + 1..at];
79 (&s[..pos], device_slice.parse::<u16>().unwrap_or(0))
80 }
81 _ => (user_part, 0),
82 };
83 return Some(ParsedJidParts {
84 user,
85 server,
86 agent: 0,
87 device,
88 integrator: 0,
89 });
90 }
91
92 if server == DEFAULT_USER_SERVER {
94 if let Some(pos) = colon_pos {
96 let user_end = pos;
97 let device_start = pos + 1;
98 let device_slice = &s[device_start..at];
99 let device = device_slice.parse::<u16>().unwrap_or(0);
100 return Some(ParsedJidParts {
101 user: &s[..user_end],
102 server,
103 agent: 0,
104 device,
105 integrator: 0,
106 });
107 }
108 if let Some(dot_pos) = last_dot_pos {
110 let suffix = &s[dot_pos + 1..at];
112 if let Ok(device_val) = suffix.parse::<u16>() {
113 return Some(ParsedJidParts {
114 user: &s[..dot_pos],
115 server,
116 agent: 0,
117 device: device_val,
118 integrator: 0,
119 });
120 }
121 }
122 return Some(ParsedJidParts {
124 user: user_part,
125 server,
126 agent: 0,
127 device: 0,
128 integrator: 0,
129 });
130 }
131
132 let (user_before_colon, device) = match colon_pos {
134 Some(pos) => {
135 let user_end = pos;
137 let device_start = pos + 1;
138 let device_slice = &s[device_start..at];
139 (&s[..user_end], device_slice.parse::<u16>().unwrap_or(0))
140 }
141 None => (user_part, 0),
142 };
143
144 let user_to_check = user_before_colon;
146 let (final_user, agent) = {
147 if let Some(dot_pos) = user_to_check.rfind('.') {
148 let suffix = &user_to_check[dot_pos + 1..];
149 if let Ok(agent_val) = suffix.parse::<u16>() {
150 if agent_val <= u8::MAX as u16 {
151 (&user_to_check[..dot_pos], agent_val as u8)
152 } else {
153 (user_to_check, 0)
154 }
155 } else {
156 (user_to_check, 0)
157 }
158 } else {
159 (user_to_check, 0)
160 }
161 };
162
163 Some(ParsedJidParts {
164 user: final_user,
165 server,
166 agent,
167 device,
168 integrator: 0,
169 })
170}
171
172pub const DEFAULT_USER_SERVER: &str = "s.whatsapp.net";
173pub const SERVER_JID: &str = "s.whatsapp.net";
174pub const GROUP_SERVER: &str = "g.us";
175pub const LEGACY_USER_SERVER: &str = "c.us";
176pub const BROADCAST_SERVER: &str = "broadcast";
177pub const HIDDEN_USER_SERVER: &str = "lid";
178pub const NEWSLETTER_SERVER: &str = "newsletter";
179pub const HOSTED_SERVER: &str = "hosted";
180pub const HOSTED_LID_SERVER: &str = "hosted.lid";
181pub const MESSENGER_SERVER: &str = "msgr";
182pub const INTEROP_SERVER: &str = "interop";
183pub const BOT_SERVER: &str = "bot";
184pub const STATUS_BROADCAST_USER: &str = "status";
185
186pub type MessageId = String;
187pub type MessageServerId = i32;
188#[derive(Debug)]
189pub enum JidError {
190 InvalidFormat(String),
192 Parse(std::num::ParseIntError),
194}
195
196impl fmt::Display for JidError {
197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198 match self {
199 JidError::InvalidFormat(s) => write!(f, "Invalid JID format: {s}"),
200 JidError::Parse(e) => write!(f, "Failed to parse component: {e}"),
201 }
202 }
203}
204
205impl std::error::Error for JidError {
206 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
207 match self {
208 JidError::Parse(e) => Some(e),
209 _ => None,
210 }
211 }
212}
213
214impl From<std::num::ParseIntError> for JidError {
216 fn from(err: std::num::ParseIntError) -> Self {
217 JidError::Parse(err)
218 }
219}
220
221pub trait JidExt {
222 fn user(&self) -> &str;
223 fn server(&self) -> &str;
224 fn device(&self) -> u16;
225 fn integrator(&self) -> u16;
226
227 fn is_ad(&self) -> bool {
228 self.device() > 0
229 && (self.server() == DEFAULT_USER_SERVER
230 || self.server() == HIDDEN_USER_SERVER
231 || self.server() == HOSTED_SERVER)
232 }
233
234 fn is_interop(&self) -> bool {
235 self.server() == INTEROP_SERVER && self.integrator() > 0
236 }
237
238 fn is_messenger(&self) -> bool {
239 self.server() == MESSENGER_SERVER && self.device() > 0
240 }
241
242 fn is_group(&self) -> bool {
243 self.server() == GROUP_SERVER
244 }
245
246 fn is_broadcast_list(&self) -> bool {
247 self.server() == BROADCAST_SERVER && self.user() != STATUS_BROADCAST_USER
248 }
249
250 fn is_status_broadcast(&self) -> bool {
251 self.server() == BROADCAST_SERVER && self.user() == STATUS_BROADCAST_USER
252 }
253
254 fn is_bot(&self) -> bool {
255 (self.server() == DEFAULT_USER_SERVER
256 && self.device() == 0
257 && (self.user().starts_with("1313555") || self.user().starts_with("131655500")))
258 || self.server() == BOT_SERVER
259 }
260
261 fn is_newsletter(&self) -> bool {
262 self.server() == NEWSLETTER_SERVER
263 }
264
265 fn is_hosted(&self) -> bool {
269 self.device() == 99 || self.server() == HOSTED_SERVER || self.server() == HOSTED_LID_SERVER
270 }
271
272 fn is_empty(&self) -> bool {
273 self.server().is_empty()
274 }
275
276 fn is_same_user_as(&self, other: &impl JidExt) -> bool {
277 self.user() == other.user()
278 }
279}
280
281#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
282#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
283pub struct Jid {
284 pub user: String,
285 pub server: String,
286 pub agent: u8,
287 pub device: u16,
288 pub integrator: u16,
289}
290
291#[derive(Debug, Clone, PartialEq, Eq, Hash)]
292pub struct JidRef<'a> {
293 pub user: Cow<'a, str>,
294 pub server: Cow<'a, str>,
295 pub agent: u8,
296 pub device: u16,
297 pub integrator: u16,
298}
299
300impl JidExt for Jid {
301 fn user(&self) -> &str {
302 &self.user
303 }
304 fn server(&self) -> &str {
305 &self.server
306 }
307 fn device(&self) -> u16 {
308 self.device
309 }
310 fn integrator(&self) -> u16 {
311 self.integrator
312 }
313}
314
315impl Jid {
316 pub fn new(user: &str, server: &str) -> Self {
317 Self {
318 user: user.to_string(),
319 server: server.to_string(),
320 ..Default::default()
321 }
322 }
323
324 pub fn pn(user: impl Into<String>) -> Self {
326 Self {
327 user: user.into(),
328 server: DEFAULT_USER_SERVER.to_string(),
329 ..Default::default()
330 }
331 }
332
333 pub fn lid(user: impl Into<String>) -> Self {
335 Self {
336 user: user.into(),
337 server: HIDDEN_USER_SERVER.to_string(),
338 ..Default::default()
339 }
340 }
341
342 pub fn status_broadcast() -> Self {
344 Self {
345 user: STATUS_BROADCAST_USER.to_string(),
346 server: BROADCAST_SERVER.to_string(),
347 agent: 0,
348 device: 0,
349 integrator: 0,
350 }
351 }
352
353 pub fn group(id: impl Into<String>) -> Self {
355 Self {
356 user: id.into(),
357 server: GROUP_SERVER.to_string(),
358 ..Default::default()
359 }
360 }
361
362 pub fn pn_device(user: impl Into<String>, device: u16) -> Self {
364 Self {
365 user: user.into(),
366 server: DEFAULT_USER_SERVER.to_string(),
367 device,
368 ..Default::default()
369 }
370 }
371
372 pub fn lid_device(user: impl Into<String>, device: u16) -> Self {
374 Self {
375 user: user.into(),
376 server: HIDDEN_USER_SERVER.to_string(),
377 device,
378 ..Default::default()
379 }
380 }
381
382 #[inline]
384 pub fn is_pn(&self) -> bool {
385 self.server == DEFAULT_USER_SERVER
386 }
387
388 #[inline]
390 pub fn is_lid(&self) -> bool {
391 self.server == HIDDEN_USER_SERVER
392 }
393
394 #[inline]
396 pub fn user_base(&self) -> &str {
397 if let Some((base, _)) = self.user.split_once(':') {
398 base
399 } else {
400 &self.user
401 }
402 }
403
404 pub fn with_device(&self, device_id: u16) -> Self {
406 Self {
407 user: self.user.clone(),
408 server: self.server.clone(),
409 agent: self.agent,
410 device: device_id,
411 integrator: self.integrator,
412 }
413 }
414
415 pub fn actual_agent(&self) -> u8 {
416 match self.server.as_str() {
417 DEFAULT_USER_SERVER => 0,
418 HIDDEN_USER_SERVER => self.agent,
423 _ => self.agent,
424 }
425 }
426
427 pub fn to_non_ad(&self) -> Self {
428 Self {
429 user: self.user.clone(),
430 server: self.server.clone(),
431 integrator: self.integrator,
432 ..Default::default()
433 }
434 }
435
436 #[inline]
439 pub fn matches_user_or_lid(&self, user: &Jid, lid: Option<&Jid>) -> bool {
440 self.is_same_user_as(user) || lid.is_some_and(|l| self.is_same_user_as(l))
441 }
442
443 pub fn normalize_for_prekey_bundle(&self) -> Self {
449 let mut jid = self.clone();
450 if jid.server == DEFAULT_USER_SERVER || jid.server == HIDDEN_USER_SERVER {
451 jid.agent = 0;
452 }
453 jid
454 }
455
456 pub fn to_ad_string(&self) -> String {
457 if self.user.is_empty() {
458 self.server.clone()
459 } else {
460 format!(
461 "{}.{}:{}@{}",
462 self.user, self.agent, self.device, self.server
463 )
464 }
465 }
466
467 #[inline]
469 pub fn device_eq(&self, other: &Jid) -> bool {
470 self.user == other.user && self.server == other.server && self.device == other.device
471 }
472
473 #[inline]
475 pub fn device_key(&self) -> DeviceKey<'_> {
476 DeviceKey {
477 user: &self.user,
478 server: &self.server,
479 device: self.device,
480 }
481 }
482}
483
484#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
486pub struct DeviceKey<'a> {
487 pub user: &'a str,
488 pub server: &'a str,
489 pub device: u16,
490}
491
492impl<'a> JidExt for JidRef<'a> {
493 fn user(&self) -> &str {
494 &self.user
495 }
496 fn server(&self) -> &str {
497 &self.server
498 }
499 fn device(&self) -> u16 {
500 self.device
501 }
502 fn integrator(&self) -> u16 {
503 self.integrator
504 }
505}
506
507impl<'a> JidRef<'a> {
508 pub fn new(user: Cow<'a, str>, server: Cow<'a, str>) -> Self {
509 Self {
510 user,
511 server,
512 agent: 0,
513 device: 0,
514 integrator: 0,
515 }
516 }
517
518 pub fn to_owned(&self) -> Jid {
519 Jid {
520 user: self.user.to_string(),
521 server: self.server.to_string(),
522 agent: self.agent,
523 device: self.device,
524 integrator: self.integrator,
525 }
526 }
527}
528
529impl FromStr for Jid {
530 type Err = JidError;
531 fn from_str(s: &str) -> Result<Self, Self::Err> {
532 if let Some(parts) = parse_jid_fast(s) {
534 return Ok(Jid {
535 user: parts.user.to_string(),
536 server: parts.server.to_string(),
537 agent: parts.agent,
538 device: parts.device,
539 integrator: parts.integrator,
540 });
541 }
542
543 let (user_part, server) = match s.split_once('@') {
546 Some((u, s)) => (u, s),
547 None => ("", s),
548 };
549
550 if user_part.is_empty() {
551 let known_servers = [
552 DEFAULT_USER_SERVER,
553 GROUP_SERVER,
554 LEGACY_USER_SERVER,
555 BROADCAST_SERVER,
556 HIDDEN_USER_SERVER,
557 NEWSLETTER_SERVER,
558 HOSTED_SERVER,
559 MESSENGER_SERVER,
560 INTEROP_SERVER,
561 BOT_SERVER,
562 STATUS_BROADCAST_USER,
563 ];
564 if !known_servers.contains(&server) {
565 return Err(JidError::InvalidFormat(format!(
566 "Invalid JID format: unknown server '{}'",
567 server
568 )));
569 }
570 }
571
572 if server == HIDDEN_USER_SERVER {
575 let (user, device) = if let Some((u, d_str)) = user_part.rsplit_once(':') {
576 (u, d_str.parse()?)
577 } else {
578 (user_part, 0)
579 };
580 return Ok(Jid {
581 user: user.to_string(),
582 server: server.to_string(),
583 device,
584 agent: 0,
585 integrator: 0,
586 });
587 }
588
589 let mut user = user_part;
591 let mut device = 0;
592 let mut agent = 0;
593
594 if let Some((u, d_str)) = user_part.rsplit_once(':') {
595 user = u;
596 device = d_str.parse()?;
597 }
598
599 if server != DEFAULT_USER_SERVER
600 && server != HIDDEN_USER_SERVER
601 && let Some((u, last_part)) = user.rsplit_once('.')
602 && let Ok(num_val) = last_part.parse::<u16>()
603 {
604 user = u;
605 agent = num_val as u8;
606 }
607
608 if let Some((u, last_part)) = user_part.rsplit_once('.')
609 && let Ok(num_val) = last_part.parse::<u16>()
610 {
611 if server == DEFAULT_USER_SERVER {
612 user = u;
613 device = num_val;
614 } else {
615 user = u;
616 if num_val > u8::MAX as u16 {
617 return Err(JidError::InvalidFormat(format!(
618 "Agent component out of range: {num_val}"
619 )));
620 }
621 agent = num_val as u8;
622 }
623 }
624
625 Ok(Jid {
626 user: user.to_string(),
627 server: server.to_string(),
628 agent,
629 device,
630 integrator: 0,
631 })
632 }
633}
634
635impl fmt::Display for Jid {
636 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
637 if self.user.is_empty() {
638 write!(f, "{}", self.server)
640 } else {
641 write!(f, "{}", self.user)?;
642
643 if self.agent > 0 {
648 let server_str = self.server(); if server_str != DEFAULT_USER_SERVER
653 && server_str != HIDDEN_USER_SERVER
654 && server_str != HOSTED_SERVER
655 {
656 write!(f, ".{}", self.agent)?;
657 }
658 }
659
660 if self.device > 0 {
661 write!(f, ":{}", self.device)?;
662 }
663
664 write!(f, "@{}", self.server)
665 }
666 }
667}
668
669impl<'a> fmt::Display for JidRef<'a> {
670 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
671 if self.user.is_empty() {
672 write!(f, "{}", self.server)
674 } else {
675 write!(f, "{}", self.user)?;
676
677 if self.agent > 0 {
682 let server_str = self.server(); if server_str != DEFAULT_USER_SERVER
687 && server_str != HIDDEN_USER_SERVER
688 && server_str != HOSTED_SERVER
689 {
690 write!(f, ".{}", self.agent)?;
691 }
692 }
693
694 if self.device > 0 {
695 write!(f, ":{}", self.device)?;
696 }
697
698 write!(f, "@{}", self.server)
699 }
700 }
701}
702
703impl From<Jid> for String {
704 fn from(jid: Jid) -> Self {
705 jid.to_string()
706 }
707}
708
709impl<'a> From<JidRef<'a>> for String {
710 fn from(jid: JidRef<'a>) -> Self {
711 jid.to_string()
712 }
713}
714
715impl TryFrom<String> for Jid {
716 type Error = JidError;
717 fn try_from(value: String) -> Result<Self, Self::Error> {
718 Jid::from_str(&value)
719 }
720}
721
722#[cfg(test)]
723mod tests {
724 use super::*;
725 use std::str::FromStr;
726
727 fn assert_jid_roundtrip(
729 input: &str,
730 expected_user: &str,
731 expected_server: &str,
732 expected_device: u16,
733 expected_agent: u8,
734 ) {
735 assert_jid_parse_and_display(
736 input,
737 expected_user,
738 expected_server,
739 expected_device,
740 expected_agent,
741 input,
742 );
743 }
744
745 fn assert_jid_parse_and_display(
747 input: &str,
748 expected_user: &str,
749 expected_server: &str,
750 expected_device: u16,
751 expected_agent: u8,
752 expected_output: &str,
753 ) {
754 let jid = Jid::from_str(input).unwrap_or_else(|_| panic!("Failed to parse JID: {}", input));
756
757 assert_eq!(
758 jid.user, expected_user,
759 "User part did not match for {}",
760 input
761 );
762 assert_eq!(
763 jid.server, expected_server,
764 "Server part did not match for {}",
765 input
766 );
767 assert_eq!(
768 jid.device, expected_device,
769 "Device part did not match for {}",
770 input
771 );
772 assert_eq!(
773 jid.agent, expected_agent,
774 "Agent part did not match for {}",
775 input
776 );
777
778 let formatted = jid.to_string();
780 assert_eq!(
781 formatted, expected_output,
782 "Formatted string did not match expected output for {}",
783 input
784 );
785 }
786
787 #[test]
788 fn test_jid_parsing_and_display_roundtrip() {
789 assert_jid_roundtrip(
791 "1234567890@s.whatsapp.net",
792 "1234567890",
793 "s.whatsapp.net",
794 0,
795 0,
796 );
797 assert_jid_roundtrip(
798 "1234567890:15@s.whatsapp.net",
799 "1234567890",
800 "s.whatsapp.net",
801 15,
802 0,
803 );
804 assert_jid_roundtrip("123-456@g.us", "123-456", "g.us", 0, 0);
805
806 assert_jid_roundtrip("s.whatsapp.net", "", "s.whatsapp.net", 0, 0);
809
810 assert_jid_roundtrip("12345.6789@lid", "12345.6789", "lid", 0, 0);
812 assert_jid_roundtrip("12345.6789:25@lid", "12345.6789", "lid", 25, 0);
813 }
814
815 #[test]
816 fn test_special_from_str_parsing() {
817 let jid = Jid::from_str("1234567890.2:15@hosted").expect("test hosted JID should be valid");
819 assert_eq!(jid.user, "1234567890");
820 assert_eq!(jid.server, "hosted");
821 assert_eq!(jid.device, 15);
822 assert_eq!(jid.agent, 2);
823 }
824
825 #[test]
826 fn test_manual_jid_formatting_edge_cases() {
827 let jid1 = Jid {
834 user: "1234567890".to_string(),
835 server: "s.whatsapp.net".to_string(),
836 device: 15,
837 agent: 2, integrator: 0,
839 };
840 assert_eq!(jid1.to_string(), "1234567890:15@s.whatsapp.net");
843
844 let jid2 = Jid {
847 user: "12345.6789".to_string(),
848 server: "lid".to_string(),
849 device: 25,
850 agent: 1, integrator: 0,
852 };
853 assert_eq!(jid2.to_string(), "12345.6789:25@lid");
856
857 let jid3 = Jid {
860 user: "1234567890".to_string(),
861 server: "hosted".to_string(),
862 device: 15,
863 agent: 2,
864 integrator: 0,
865 };
866 assert_eq!(jid3.to_string(), "1234567890:15@hosted");
869
870 let jid4 = Jid {
872 user: "user".to_string(),
873 server: "custom.net".to_string(),
874 device: 10,
875 agent: 5,
876 integrator: 0,
877 };
878 assert_eq!(jid4.to_string(), "user.5:10@custom.net");
880 }
881
882 #[test]
883 fn test_invalid_jids_should_fail_to_parse() {
884 assert!(Jid::from_str("thisisnotajid").is_err());
885 assert!(Jid::from_str("").is_err());
886 assert!(Jid::from_str("@s.whatsapp.net").is_ok());
888 assert!(Jid::from_str("@unknown.server").is_err());
890 assert!(Jid::from_str("2").is_err());
893 }
894
895 #[test]
923 fn test_is_hosted_device_detection() {
924 let cloud_api_device: Jid = "5511999887766:99@s.whatsapp.net"
929 .parse()
930 .expect("test JID should be valid");
931 assert!(
932 cloud_api_device.is_hosted(),
933 "Device ID 99 on s.whatsapp.net should be detected as hosted (Cloud API)"
934 );
935
936 let cloud_api_lid: Jid = "100000012345678:99@lid"
938 .parse()
939 .expect("test JID should be valid");
940 assert!(
941 cloud_api_lid.is_hosted(),
942 "Device ID 99 on lid server should be detected as hosted"
943 );
944
945 let hosted_server: Jid = "5511999887766:99@hosted"
947 .parse()
948 .expect("test JID should be valid");
949 assert!(
950 hosted_server.is_hosted(),
951 "JID with @hosted server should be detected as hosted"
952 );
953
954 let hosted_lid_server: Jid = "100000012345678:99@hosted.lid"
956 .parse()
957 .expect("test JID should be valid");
958 assert!(
959 hosted_lid_server.is_hosted(),
960 "JID with @hosted.lid server should be detected as hosted"
961 );
962
963 let hosted_server_other_device: Jid = "5511999887766:0@hosted"
966 .parse()
967 .expect("test JID should be valid");
968 assert!(
969 hosted_server_other_device.is_hosted(),
970 "JID with @hosted server should be hosted regardless of device ID"
971 );
972
973 let regular_phone: Jid = "5511999887766:0@s.whatsapp.net"
977 .parse()
978 .expect("test JID should be valid");
979 assert!(
980 !regular_phone.is_hosted(),
981 "Regular phone device (ID 0) should NOT be hosted"
982 );
983
984 let companion_device: Jid = "5511999887766:33@s.whatsapp.net"
986 .parse()
987 .expect("test JID should be valid");
988 assert!(
989 !companion_device.is_hosted(),
990 "Companion device (ID 33) should NOT be hosted"
991 );
992
993 let regular_lid: Jid = "100000012345678:0@lid"
995 .parse()
996 .expect("test JID should be valid");
997 assert!(
998 !regular_lid.is_hosted(),
999 "Regular LID device should NOT be hosted"
1000 );
1001
1002 let lid_companion: Jid = "100000012345678:33@lid"
1004 .parse()
1005 .expect("test JID should be valid");
1006 assert!(
1007 !lid_companion.is_hosted(),
1008 "LID companion device (ID 33) should NOT be hosted"
1009 );
1010
1011 let group_jid: Jid = "120363012345678@g.us"
1013 .parse()
1014 .expect("test JID should be valid");
1015 assert!(
1016 !group_jid.is_hosted(),
1017 "Group JID should NOT be detected as hosted"
1018 );
1019
1020 let user_jid: Jid = "5511999887766@s.whatsapp.net"
1022 .parse()
1023 .expect("test JID should be valid");
1024 assert!(
1025 !user_jid.is_hosted(),
1026 "User JID without device should NOT be hosted"
1027 );
1028
1029 let bot_jid: Jid = "13136555001:0@s.whatsapp.net"
1031 .parse()
1032 .expect("test JID should be valid");
1033 assert!(
1034 !bot_jid.is_hosted(),
1035 "Bot JID should NOT be detected as hosted (different mechanism)"
1036 );
1037 }
1038
1039 #[test]
1050 fn test_hosted_device_filtering_for_groups() {
1051 let devices: Vec<Jid> = vec![
1053 "5511999887766:0@s.whatsapp.net"
1055 .parse()
1056 .expect("test JID should be valid"), "5511999887766:33@s.whatsapp.net"
1058 .parse()
1059 .expect("test JID should be valid"), "5521988776655:0@s.whatsapp.net"
1061 .parse()
1062 .expect("test JID should be valid"), "100000012345678:0@lid"
1064 .parse()
1065 .expect("test JID should be valid"), "100000012345678:33@lid"
1067 .parse()
1068 .expect("test JID should be valid"), "5531977665544:99@s.whatsapp.net"
1071 .parse()
1072 .expect("test JID should be valid"), "100000087654321:99@lid"
1074 .parse()
1075 .expect("test JID should be valid"), "5541966554433:99@hosted"
1077 .parse()
1078 .expect("test JID should be valid"), ];
1080
1081 let filtered: Vec<&Jid> = devices.iter().filter(|jid| !jid.is_hosted()).collect();
1083
1084 assert_eq!(
1086 filtered.len(),
1087 5,
1088 "Should have 5 non-hosted devices after filtering"
1089 );
1090
1091 for jid in &filtered {
1093 assert!(
1094 !jid.is_hosted(),
1095 "Filtered list should not contain hosted devices: {}",
1096 jid
1097 );
1098 }
1099
1100 let hosted_count = devices.iter().filter(|jid| jid.is_hosted()).count();
1102 assert_eq!(hosted_count, 3, "Should have filtered out 3 hosted devices");
1103 }
1104
1105 #[test]
1106 fn test_jid_pn_factory() {
1107 let jid = Jid::pn("1234567890");
1108 assert_eq!(jid.user, "1234567890");
1109 assert_eq!(jid.server, DEFAULT_USER_SERVER);
1110 assert_eq!(jid.device, 0);
1111 assert!(jid.is_pn());
1112 }
1113
1114 #[test]
1115 fn test_jid_lid_factory() {
1116 let jid = Jid::lid("100000012345678");
1117 assert_eq!(jid.user, "100000012345678");
1118 assert_eq!(jid.server, HIDDEN_USER_SERVER);
1119 assert_eq!(jid.device, 0);
1120 assert!(jid.is_lid());
1121 }
1122
1123 #[test]
1124 fn test_jid_group_factory() {
1125 let jid = Jid::group("123456789-1234567890");
1126 assert_eq!(jid.user, "123456789-1234567890");
1127 assert_eq!(jid.server, GROUP_SERVER);
1128 assert!(jid.is_group());
1129 }
1130
1131 #[test]
1132 fn test_jid_pn_device_factory() {
1133 let jid = Jid::pn_device("1234567890", 5);
1134 assert_eq!(jid.user, "1234567890");
1135 assert_eq!(jid.server, DEFAULT_USER_SERVER);
1136 assert_eq!(jid.device, 5);
1137 assert!(jid.is_pn());
1138 assert!(jid.is_ad());
1139 }
1140
1141 #[test]
1142 fn test_jid_lid_device_factory() {
1143 let jid = Jid::lid_device("100000012345678", 33);
1144 assert_eq!(jid.user, "100000012345678");
1145 assert_eq!(jid.server, HIDDEN_USER_SERVER);
1146 assert_eq!(jid.device, 33);
1147 assert!(jid.is_lid());
1148 assert!(jid.is_ad());
1149 }
1150
1151 #[test]
1152 fn test_status_broadcast_jid() {
1153 let jid = Jid::status_broadcast();
1154 assert_eq!(jid.user, STATUS_BROADCAST_USER);
1155 assert_eq!(jid.server, BROADCAST_SERVER);
1156 assert_eq!(jid.device, 0);
1157 assert!(jid.is_status_broadcast());
1158 assert!(!jid.is_group());
1159 assert!(!jid.is_broadcast_list());
1160 assert_eq!(jid.to_string(), "status@broadcast");
1161
1162 let parsed: Jid = "status@broadcast".parse().expect("should parse");
1164 assert!(parsed.is_status_broadcast());
1165 assert_eq!(parsed.user, "status");
1166 assert_eq!(parsed.server, "broadcast");
1167
1168 let broadcast_list = Jid::new("12345", BROADCAST_SERVER);
1170 assert!(broadcast_list.is_broadcast_list());
1171 assert!(!broadcast_list.is_status_broadcast());
1172 }
1173
1174 #[test]
1175 fn test_jid_to_non_ad_preserves_user_server() {
1176 let device_jid = Jid::pn_device("1234567890", 33);
1178 let non_ad = device_jid.to_non_ad();
1179 assert_eq!(non_ad.user, "1234567890");
1180 assert_eq!(non_ad.server, DEFAULT_USER_SERVER);
1181 assert_eq!(non_ad.device, 0);
1182 assert!(!non_ad.is_ad());
1183
1184 let lid_device = Jid::lid_device("100000012345678", 25);
1186 let lid_non_ad = lid_device.to_non_ad();
1187 assert_eq!(lid_non_ad.user, "100000012345678");
1188 assert_eq!(lid_non_ad.server, HIDDEN_USER_SERVER);
1189 assert_eq!(lid_non_ad.device, 0);
1190
1191 let status = Jid::status_broadcast();
1193 let status_non_ad = status.to_non_ad();
1194 assert_eq!(status_non_ad.to_string(), "status@broadcast");
1195 }
1196
1197 #[test]
1198 fn test_jid_factories_with_string_types() {
1199 let jid1 = Jid::pn("123");
1201 assert_eq!(jid1.user, "123");
1202
1203 let jid2 = Jid::lid(String::from("456"));
1205 assert_eq!(jid2.user, "456");
1206
1207 let user = "789".to_string();
1209 let jid3 = Jid::group(user);
1210 assert_eq!(jid3.user, "789");
1211 }
1212}