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 group(id: impl Into<String>) -> Self {
344 Self {
345 user: id.into(),
346 server: GROUP_SERVER.to_string(),
347 ..Default::default()
348 }
349 }
350
351 pub fn pn_device(user: impl Into<String>, device: u16) -> Self {
353 Self {
354 user: user.into(),
355 server: DEFAULT_USER_SERVER.to_string(),
356 device,
357 ..Default::default()
358 }
359 }
360
361 pub fn lid_device(user: impl Into<String>, device: u16) -> Self {
363 Self {
364 user: user.into(),
365 server: HIDDEN_USER_SERVER.to_string(),
366 device,
367 ..Default::default()
368 }
369 }
370
371 #[inline]
373 pub fn is_pn(&self) -> bool {
374 self.server == DEFAULT_USER_SERVER
375 }
376
377 #[inline]
379 pub fn is_lid(&self) -> bool {
380 self.server == HIDDEN_USER_SERVER
381 }
382
383 #[inline]
385 pub fn user_base(&self) -> &str {
386 if let Some((base, _)) = self.user.split_once(':') {
387 base
388 } else {
389 &self.user
390 }
391 }
392
393 pub fn with_device(&self, device_id: u16) -> Self {
395 Self {
396 user: self.user.clone(),
397 server: self.server.clone(),
398 agent: self.agent,
399 device: device_id,
400 integrator: self.integrator,
401 }
402 }
403
404 pub fn actual_agent(&self) -> u8 {
405 match self.server.as_str() {
406 DEFAULT_USER_SERVER => 0,
407 HIDDEN_USER_SERVER => self.agent,
412 _ => self.agent,
413 }
414 }
415
416 pub fn to_non_ad(&self) -> Self {
417 Self {
418 user: self.user.clone(),
419 server: self.server.clone(),
420 integrator: self.integrator,
421 ..Default::default()
422 }
423 }
424
425 #[inline]
428 pub fn matches_user_or_lid(&self, user: &Jid, lid: Option<&Jid>) -> bool {
429 self.is_same_user_as(user) || lid.is_some_and(|l| self.is_same_user_as(l))
430 }
431
432 pub fn normalize_for_prekey_bundle(&self) -> Self {
438 let mut jid = self.clone();
439 if jid.server == DEFAULT_USER_SERVER || jid.server == HIDDEN_USER_SERVER {
440 jid.agent = 0;
441 }
442 jid
443 }
444
445 pub fn to_ad_string(&self) -> String {
446 if self.user.is_empty() {
447 self.server.clone()
448 } else {
449 format!(
450 "{}.{}:{}@{}",
451 self.user, self.agent, self.device, self.server
452 )
453 }
454 }
455
456 #[inline]
458 pub fn device_eq(&self, other: &Jid) -> bool {
459 self.user == other.user && self.server == other.server && self.device == other.device
460 }
461
462 #[inline]
464 pub fn device_key(&self) -> DeviceKey<'_> {
465 DeviceKey {
466 user: &self.user,
467 server: &self.server,
468 device: self.device,
469 }
470 }
471}
472
473#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
475pub struct DeviceKey<'a> {
476 pub user: &'a str,
477 pub server: &'a str,
478 pub device: u16,
479}
480
481impl<'a> JidExt for JidRef<'a> {
482 fn user(&self) -> &str {
483 &self.user
484 }
485 fn server(&self) -> &str {
486 &self.server
487 }
488 fn device(&self) -> u16 {
489 self.device
490 }
491 fn integrator(&self) -> u16 {
492 self.integrator
493 }
494}
495
496impl<'a> JidRef<'a> {
497 pub fn new(user: Cow<'a, str>, server: Cow<'a, str>) -> Self {
498 Self {
499 user,
500 server,
501 agent: 0,
502 device: 0,
503 integrator: 0,
504 }
505 }
506
507 pub fn to_owned(&self) -> Jid {
508 Jid {
509 user: self.user.to_string(),
510 server: self.server.to_string(),
511 agent: self.agent,
512 device: self.device,
513 integrator: self.integrator,
514 }
515 }
516}
517
518impl FromStr for Jid {
519 type Err = JidError;
520 fn from_str(s: &str) -> Result<Self, Self::Err> {
521 if let Some(parts) = parse_jid_fast(s) {
523 return Ok(Jid {
524 user: parts.user.to_string(),
525 server: parts.server.to_string(),
526 agent: parts.agent,
527 device: parts.device,
528 integrator: parts.integrator,
529 });
530 }
531
532 let (user_part, server) = match s.split_once('@') {
535 Some((u, s)) => (u, s),
536 None => ("", s),
537 };
538
539 if user_part.is_empty() {
540 let known_servers = [
541 DEFAULT_USER_SERVER,
542 GROUP_SERVER,
543 LEGACY_USER_SERVER,
544 BROADCAST_SERVER,
545 HIDDEN_USER_SERVER,
546 NEWSLETTER_SERVER,
547 HOSTED_SERVER,
548 MESSENGER_SERVER,
549 INTEROP_SERVER,
550 BOT_SERVER,
551 STATUS_BROADCAST_USER,
552 ];
553 if !known_servers.contains(&server) {
554 return Err(JidError::InvalidFormat(format!(
555 "Invalid JID format: unknown server '{}'",
556 server
557 )));
558 }
559 }
560
561 if server == HIDDEN_USER_SERVER {
564 let (user, device) = if let Some((u, d_str)) = user_part.rsplit_once(':') {
565 (u, d_str.parse()?)
566 } else {
567 (user_part, 0)
568 };
569 return Ok(Jid {
570 user: user.to_string(),
571 server: server.to_string(),
572 device,
573 agent: 0,
574 integrator: 0,
575 });
576 }
577
578 let mut user = user_part;
580 let mut device = 0;
581 let mut agent = 0;
582
583 if let Some((u, d_str)) = user_part.rsplit_once(':') {
584 user = u;
585 device = d_str.parse()?;
586 }
587
588 if server != DEFAULT_USER_SERVER
589 && server != HIDDEN_USER_SERVER
590 && let Some((u, last_part)) = user.rsplit_once('.')
591 && let Ok(num_val) = last_part.parse::<u16>()
592 {
593 user = u;
594 agent = num_val as u8;
595 }
596
597 if let Some((u, last_part)) = user_part.rsplit_once('.')
598 && let Ok(num_val) = last_part.parse::<u16>()
599 {
600 if server == DEFAULT_USER_SERVER {
601 user = u;
602 device = num_val;
603 } else {
604 user = u;
605 if num_val > u8::MAX as u16 {
606 return Err(JidError::InvalidFormat(format!(
607 "Agent component out of range: {num_val}"
608 )));
609 }
610 agent = num_val as u8;
611 }
612 }
613
614 Ok(Jid {
615 user: user.to_string(),
616 server: server.to_string(),
617 agent,
618 device,
619 integrator: 0,
620 })
621 }
622}
623
624impl fmt::Display for Jid {
625 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
626 if self.user.is_empty() {
627 write!(f, "{}", self.server)
629 } else {
630 write!(f, "{}", self.user)?;
631
632 if self.agent > 0 {
637 let server_str = self.server(); if server_str != DEFAULT_USER_SERVER
642 && server_str != HIDDEN_USER_SERVER
643 && server_str != HOSTED_SERVER
644 {
645 write!(f, ".{}", self.agent)?;
646 }
647 }
648
649 if self.device > 0 {
650 write!(f, ":{}", self.device)?;
651 }
652
653 write!(f, "@{}", self.server)
654 }
655 }
656}
657
658impl<'a> fmt::Display for JidRef<'a> {
659 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
660 if self.user.is_empty() {
661 write!(f, "{}", self.server)
663 } else {
664 write!(f, "{}", self.user)?;
665
666 if self.agent > 0 {
671 let server_str = self.server(); if server_str != DEFAULT_USER_SERVER
676 && server_str != HIDDEN_USER_SERVER
677 && server_str != HOSTED_SERVER
678 {
679 write!(f, ".{}", self.agent)?;
680 }
681 }
682
683 if self.device > 0 {
684 write!(f, ":{}", self.device)?;
685 }
686
687 write!(f, "@{}", self.server)
688 }
689 }
690}
691
692impl From<Jid> for String {
693 fn from(jid: Jid) -> Self {
694 jid.to_string()
695 }
696}
697
698impl<'a> From<JidRef<'a>> for String {
699 fn from(jid: JidRef<'a>) -> Self {
700 jid.to_string()
701 }
702}
703
704impl TryFrom<String> for Jid {
705 type Error = JidError;
706 fn try_from(value: String) -> Result<Self, Self::Error> {
707 Jid::from_str(&value)
708 }
709}
710
711#[cfg(test)]
712mod tests {
713 use super::*;
714 use std::str::FromStr;
715
716 fn assert_jid_roundtrip(
718 input: &str,
719 expected_user: &str,
720 expected_server: &str,
721 expected_device: u16,
722 expected_agent: u8,
723 ) {
724 assert_jid_parse_and_display(
725 input,
726 expected_user,
727 expected_server,
728 expected_device,
729 expected_agent,
730 input,
731 );
732 }
733
734 fn assert_jid_parse_and_display(
736 input: &str,
737 expected_user: &str,
738 expected_server: &str,
739 expected_device: u16,
740 expected_agent: u8,
741 expected_output: &str,
742 ) {
743 let jid = Jid::from_str(input).unwrap_or_else(|_| panic!("Failed to parse JID: {}", input));
745
746 assert_eq!(
747 jid.user, expected_user,
748 "User part did not match for {}",
749 input
750 );
751 assert_eq!(
752 jid.server, expected_server,
753 "Server part did not match for {}",
754 input
755 );
756 assert_eq!(
757 jid.device, expected_device,
758 "Device part did not match for {}",
759 input
760 );
761 assert_eq!(
762 jid.agent, expected_agent,
763 "Agent part did not match for {}",
764 input
765 );
766
767 let formatted = jid.to_string();
769 assert_eq!(
770 formatted, expected_output,
771 "Formatted string did not match expected output for {}",
772 input
773 );
774 }
775
776 #[test]
777 fn test_jid_parsing_and_display_roundtrip() {
778 assert_jid_roundtrip(
780 "1234567890@s.whatsapp.net",
781 "1234567890",
782 "s.whatsapp.net",
783 0,
784 0,
785 );
786 assert_jid_roundtrip(
787 "1234567890:15@s.whatsapp.net",
788 "1234567890",
789 "s.whatsapp.net",
790 15,
791 0,
792 );
793 assert_jid_roundtrip("123-456@g.us", "123-456", "g.us", 0, 0);
794
795 assert_jid_roundtrip("s.whatsapp.net", "", "s.whatsapp.net", 0, 0);
798
799 assert_jid_roundtrip("12345.6789@lid", "12345.6789", "lid", 0, 0);
801 assert_jid_roundtrip("12345.6789:25@lid", "12345.6789", "lid", 25, 0);
802 }
803
804 #[test]
805 fn test_special_from_str_parsing() {
806 let jid = Jid::from_str("1234567890.2:15@hosted").expect("test hosted JID should be valid");
808 assert_eq!(jid.user, "1234567890");
809 assert_eq!(jid.server, "hosted");
810 assert_eq!(jid.device, 15);
811 assert_eq!(jid.agent, 2);
812 }
813
814 #[test]
815 fn test_manual_jid_formatting_edge_cases() {
816 let jid1 = Jid {
823 user: "1234567890".to_string(),
824 server: "s.whatsapp.net".to_string(),
825 device: 15,
826 agent: 2, integrator: 0,
828 };
829 assert_eq!(jid1.to_string(), "1234567890:15@s.whatsapp.net");
832
833 let jid2 = Jid {
836 user: "12345.6789".to_string(),
837 server: "lid".to_string(),
838 device: 25,
839 agent: 1, integrator: 0,
841 };
842 assert_eq!(jid2.to_string(), "12345.6789:25@lid");
845
846 let jid3 = Jid {
849 user: "1234567890".to_string(),
850 server: "hosted".to_string(),
851 device: 15,
852 agent: 2,
853 integrator: 0,
854 };
855 assert_eq!(jid3.to_string(), "1234567890:15@hosted");
858
859 let jid4 = Jid {
861 user: "user".to_string(),
862 server: "custom.net".to_string(),
863 device: 10,
864 agent: 5,
865 integrator: 0,
866 };
867 assert_eq!(jid4.to_string(), "user.5:10@custom.net");
869 }
870
871 #[test]
872 fn test_invalid_jids_should_fail_to_parse() {
873 assert!(Jid::from_str("thisisnotajid").is_err());
874 assert!(Jid::from_str("").is_err());
875 assert!(Jid::from_str("@s.whatsapp.net").is_ok());
877 assert!(Jid::from_str("@unknown.server").is_err());
879 assert!(Jid::from_str("2").is_err());
882 }
883
884 #[test]
912 fn test_is_hosted_device_detection() {
913 let cloud_api_device: Jid = "5511999887766:99@s.whatsapp.net"
918 .parse()
919 .expect("test JID should be valid");
920 assert!(
921 cloud_api_device.is_hosted(),
922 "Device ID 99 on s.whatsapp.net should be detected as hosted (Cloud API)"
923 );
924
925 let cloud_api_lid: Jid = "100000012345678:99@lid"
927 .parse()
928 .expect("test JID should be valid");
929 assert!(
930 cloud_api_lid.is_hosted(),
931 "Device ID 99 on lid server should be detected as hosted"
932 );
933
934 let hosted_server: Jid = "5511999887766:99@hosted"
936 .parse()
937 .expect("test JID should be valid");
938 assert!(
939 hosted_server.is_hosted(),
940 "JID with @hosted server should be detected as hosted"
941 );
942
943 let hosted_lid_server: Jid = "100000012345678:99@hosted.lid"
945 .parse()
946 .expect("test JID should be valid");
947 assert!(
948 hosted_lid_server.is_hosted(),
949 "JID with @hosted.lid server should be detected as hosted"
950 );
951
952 let hosted_server_other_device: Jid = "5511999887766:0@hosted"
955 .parse()
956 .expect("test JID should be valid");
957 assert!(
958 hosted_server_other_device.is_hosted(),
959 "JID with @hosted server should be hosted regardless of device ID"
960 );
961
962 let regular_phone: Jid = "5511999887766:0@s.whatsapp.net"
966 .parse()
967 .expect("test JID should be valid");
968 assert!(
969 !regular_phone.is_hosted(),
970 "Regular phone device (ID 0) should NOT be hosted"
971 );
972
973 let companion_device: Jid = "5511999887766:33@s.whatsapp.net"
975 .parse()
976 .expect("test JID should be valid");
977 assert!(
978 !companion_device.is_hosted(),
979 "Companion device (ID 33) should NOT be hosted"
980 );
981
982 let regular_lid: Jid = "100000012345678:0@lid"
984 .parse()
985 .expect("test JID should be valid");
986 assert!(
987 !regular_lid.is_hosted(),
988 "Regular LID device should NOT be hosted"
989 );
990
991 let lid_companion: Jid = "100000012345678:33@lid"
993 .parse()
994 .expect("test JID should be valid");
995 assert!(
996 !lid_companion.is_hosted(),
997 "LID companion device (ID 33) should NOT be hosted"
998 );
999
1000 let group_jid: Jid = "120363012345678@g.us"
1002 .parse()
1003 .expect("test JID should be valid");
1004 assert!(
1005 !group_jid.is_hosted(),
1006 "Group JID should NOT be detected as hosted"
1007 );
1008
1009 let user_jid: Jid = "5511999887766@s.whatsapp.net"
1011 .parse()
1012 .expect("test JID should be valid");
1013 assert!(
1014 !user_jid.is_hosted(),
1015 "User JID without device should NOT be hosted"
1016 );
1017
1018 let bot_jid: Jid = "13136555001:0@s.whatsapp.net"
1020 .parse()
1021 .expect("test JID should be valid");
1022 assert!(
1023 !bot_jid.is_hosted(),
1024 "Bot JID should NOT be detected as hosted (different mechanism)"
1025 );
1026 }
1027
1028 #[test]
1039 fn test_hosted_device_filtering_for_groups() {
1040 let devices: Vec<Jid> = vec![
1042 "5511999887766:0@s.whatsapp.net"
1044 .parse()
1045 .expect("test JID should be valid"), "5511999887766:33@s.whatsapp.net"
1047 .parse()
1048 .expect("test JID should be valid"), "5521988776655:0@s.whatsapp.net"
1050 .parse()
1051 .expect("test JID should be valid"), "100000012345678:0@lid"
1053 .parse()
1054 .expect("test JID should be valid"), "100000012345678:33@lid"
1056 .parse()
1057 .expect("test JID should be valid"), "5531977665544:99@s.whatsapp.net"
1060 .parse()
1061 .expect("test JID should be valid"), "100000087654321:99@lid"
1063 .parse()
1064 .expect("test JID should be valid"), "5541966554433:99@hosted"
1066 .parse()
1067 .expect("test JID should be valid"), ];
1069
1070 let filtered: Vec<&Jid> = devices.iter().filter(|jid| !jid.is_hosted()).collect();
1072
1073 assert_eq!(
1075 filtered.len(),
1076 5,
1077 "Should have 5 non-hosted devices after filtering"
1078 );
1079
1080 for jid in &filtered {
1082 assert!(
1083 !jid.is_hosted(),
1084 "Filtered list should not contain hosted devices: {}",
1085 jid
1086 );
1087 }
1088
1089 let hosted_count = devices.iter().filter(|jid| jid.is_hosted()).count();
1091 assert_eq!(hosted_count, 3, "Should have filtered out 3 hosted devices");
1092 }
1093
1094 #[test]
1095 fn test_jid_pn_factory() {
1096 let jid = Jid::pn("1234567890");
1097 assert_eq!(jid.user, "1234567890");
1098 assert_eq!(jid.server, DEFAULT_USER_SERVER);
1099 assert_eq!(jid.device, 0);
1100 assert!(jid.is_pn());
1101 }
1102
1103 #[test]
1104 fn test_jid_lid_factory() {
1105 let jid = Jid::lid("100000012345678");
1106 assert_eq!(jid.user, "100000012345678");
1107 assert_eq!(jid.server, HIDDEN_USER_SERVER);
1108 assert_eq!(jid.device, 0);
1109 assert!(jid.is_lid());
1110 }
1111
1112 #[test]
1113 fn test_jid_group_factory() {
1114 let jid = Jid::group("123456789-1234567890");
1115 assert_eq!(jid.user, "123456789-1234567890");
1116 assert_eq!(jid.server, GROUP_SERVER);
1117 assert!(jid.is_group());
1118 }
1119
1120 #[test]
1121 fn test_jid_pn_device_factory() {
1122 let jid = Jid::pn_device("1234567890", 5);
1123 assert_eq!(jid.user, "1234567890");
1124 assert_eq!(jid.server, DEFAULT_USER_SERVER);
1125 assert_eq!(jid.device, 5);
1126 assert!(jid.is_pn());
1127 assert!(jid.is_ad());
1128 }
1129
1130 #[test]
1131 fn test_jid_lid_device_factory() {
1132 let jid = Jid::lid_device("100000012345678", 33);
1133 assert_eq!(jid.user, "100000012345678");
1134 assert_eq!(jid.server, HIDDEN_USER_SERVER);
1135 assert_eq!(jid.device, 33);
1136 assert!(jid.is_lid());
1137 assert!(jid.is_ad());
1138 }
1139
1140 #[test]
1141 fn test_jid_factories_with_string_types() {
1142 let jid1 = Jid::pn("123");
1144 assert_eq!(jid1.user, "123");
1145
1146 let jid2 = Jid::lid(String::from("456"));
1148 assert_eq!(jid2.user, "456");
1149
1150 let user = "789".to_string();
1152 let jid3 = Jid::group(user);
1153 assert_eq!(jid3.user, "789");
1154 }
1155}