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: Cow<'static, str>,
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: cow_server_from_str(server),
320 ..Default::default()
321 }
322 }
323
324 pub fn pn(user: impl Into<String>) -> Self {
326 Self {
327 user: user.into(),
328 server: Cow::Borrowed(DEFAULT_USER_SERVER),
329 ..Default::default()
330 }
331 }
332
333 pub fn lid(user: impl Into<String>) -> Self {
335 Self {
336 user: user.into(),
337 server: Cow::Borrowed(HIDDEN_USER_SERVER),
338 ..Default::default()
339 }
340 }
341
342 pub fn status_broadcast() -> Self {
344 Self {
345 user: STATUS_BROADCAST_USER.to_string(),
346 server: Cow::Borrowed(BROADCAST_SERVER),
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: Cow::Borrowed(GROUP_SERVER),
358 ..Default::default()
359 }
360 }
361
362 pub fn newsletter(id: impl Into<String>) -> Self {
364 Self {
365 user: id.into(),
366 server: Cow::Borrowed(NEWSLETTER_SERVER),
367 ..Default::default()
368 }
369 }
370
371 pub fn pn_device(user: impl Into<String>, device: u16) -> Self {
373 Self {
374 user: user.into(),
375 server: Cow::Borrowed(DEFAULT_USER_SERVER),
376 device,
377 ..Default::default()
378 }
379 }
380
381 pub fn lid_device(user: impl Into<String>, device: u16) -> Self {
383 Self {
384 user: user.into(),
385 server: Cow::Borrowed(HIDDEN_USER_SERVER),
386 device,
387 ..Default::default()
388 }
389 }
390
391 #[inline]
393 pub fn is_pn(&self) -> bool {
394 self.server == DEFAULT_USER_SERVER
395 }
396
397 #[inline]
399 pub fn is_lid(&self) -> bool {
400 self.server == HIDDEN_USER_SERVER
401 }
402
403 #[inline]
405 pub fn user_base(&self) -> &str {
406 if let Some((base, _)) = self.user.split_once(':') {
407 base
408 } else {
409 &self.user
410 }
411 }
412
413 pub fn with_device(&self, device_id: u16) -> Self {
415 Self {
416 user: self.user.clone(),
417 server: self.server.clone(),
418 agent: self.agent,
419 device: device_id,
420 integrator: self.integrator,
421 }
422 }
423
424 pub fn actual_agent(&self) -> u8 {
425 match &*self.server {
426 DEFAULT_USER_SERVER => 0,
427 HIDDEN_USER_SERVER => self.agent,
432 _ => self.agent,
433 }
434 }
435
436 pub fn to_non_ad(&self) -> Self {
437 Self {
438 user: self.user.clone(),
439 server: self.server.clone(),
440 integrator: self.integrator,
441 ..Default::default()
442 }
443 }
444
445 #[inline]
448 pub fn matches_user_or_lid(&self, user: &Jid, lid: Option<&Jid>) -> bool {
449 self.is_same_user_as(user) || lid.is_some_and(|l| self.is_same_user_as(l))
450 }
451
452 pub fn normalize_for_prekey_bundle(&self) -> Self {
458 let mut jid = self.clone();
459 if jid.server == DEFAULT_USER_SERVER || jid.server == HIDDEN_USER_SERVER {
460 jid.agent = 0;
461 }
462 jid
463 }
464
465 pub fn to_ad_string(&self) -> String {
466 if self.user.is_empty() {
467 self.server.to_string()
468 } else {
469 format!(
470 "{}.{}:{}@{}",
471 self.user, self.agent, self.device, self.server
472 )
473 }
474 }
475
476 #[inline]
478 pub fn device_eq(&self, other: &Jid) -> bool {
479 self.user == other.user && self.server == other.server && self.device == other.device
480 }
481
482 #[inline]
484 pub fn device_key(&self) -> DeviceKey<'_> {
485 DeviceKey {
486 user: &self.user,
487 server: &self.server,
488 device: self.device,
489 }
490 }
491}
492
493#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
495pub struct DeviceKey<'a> {
496 pub user: &'a str,
497 pub server: &'a str,
498 pub device: u16,
499}
500
501impl<'a> JidExt for JidRef<'a> {
502 fn user(&self) -> &str {
503 &self.user
504 }
505 fn server(&self) -> &str {
506 &self.server
507 }
508 fn device(&self) -> u16 {
509 self.device
510 }
511 fn integrator(&self) -> u16 {
512 self.integrator
513 }
514}
515
516impl<'a> JidRef<'a> {
517 pub fn new(user: Cow<'a, str>, server: Cow<'a, str>) -> Self {
518 Self {
519 user,
520 server,
521 agent: 0,
522 device: 0,
523 integrator: 0,
524 }
525 }
526
527 pub fn to_owned(&self) -> Jid {
528 Jid {
529 user: self.user.to_string(),
530 server: cow_server_from_str(&self.server),
531 agent: self.agent,
532 device: self.device,
533 integrator: self.integrator,
534 }
535 }
536}
537
538#[inline]
540pub fn cow_server_from_str(server: &str) -> Cow<'static, str> {
541 match server {
542 DEFAULT_USER_SERVER => Cow::Borrowed(DEFAULT_USER_SERVER),
543 HIDDEN_USER_SERVER => Cow::Borrowed(HIDDEN_USER_SERVER),
544 GROUP_SERVER => Cow::Borrowed(GROUP_SERVER),
545 BROADCAST_SERVER => Cow::Borrowed(BROADCAST_SERVER),
546 LEGACY_USER_SERVER => Cow::Borrowed(LEGACY_USER_SERVER),
547 NEWSLETTER_SERVER => Cow::Borrowed(NEWSLETTER_SERVER),
548 HOSTED_SERVER => Cow::Borrowed(HOSTED_SERVER),
549 HOSTED_LID_SERVER => Cow::Borrowed(HOSTED_LID_SERVER),
550 MESSENGER_SERVER => Cow::Borrowed(MESSENGER_SERVER),
551 INTEROP_SERVER => Cow::Borrowed(INTEROP_SERVER),
552 BOT_SERVER => Cow::Borrowed(BOT_SERVER),
553 other => Cow::Owned(other.to_string()),
554 }
555}
556
557impl FromStr for Jid {
558 type Err = JidError;
559 fn from_str(s: &str) -> Result<Self, Self::Err> {
560 if let Some(parts) = parse_jid_fast(s) {
562 return Ok(Jid {
563 user: parts.user.to_string(),
564 server: cow_server_from_str(parts.server),
565 agent: parts.agent,
566 device: parts.device,
567 integrator: parts.integrator,
568 });
569 }
570
571 let (user_part, server) = match s.split_once('@') {
574 Some((u, s)) => (u, s),
575 None => ("", s),
576 };
577
578 if user_part.is_empty() {
579 let known_servers = [
580 DEFAULT_USER_SERVER,
581 GROUP_SERVER,
582 LEGACY_USER_SERVER,
583 BROADCAST_SERVER,
584 HIDDEN_USER_SERVER,
585 NEWSLETTER_SERVER,
586 HOSTED_SERVER,
587 MESSENGER_SERVER,
588 INTEROP_SERVER,
589 BOT_SERVER,
590 STATUS_BROADCAST_USER,
591 ];
592 if !known_servers.contains(&server) {
593 return Err(JidError::InvalidFormat(format!(
594 "Invalid JID format: unknown server '{}'",
595 server
596 )));
597 }
598 }
599
600 if server == HIDDEN_USER_SERVER {
603 let (user, device) = if let Some((u, d_str)) = user_part.rsplit_once(':') {
604 (u, d_str.parse()?)
605 } else {
606 (user_part, 0)
607 };
608 return Ok(Jid {
609 user: user.to_string(),
610 server: cow_server_from_str(server),
611 device,
612 agent: 0,
613 integrator: 0,
614 });
615 }
616
617 let mut user = user_part;
619 let mut device = 0;
620 let mut agent = 0;
621
622 if let Some((u, d_str)) = user_part.rsplit_once(':') {
623 user = u;
624 device = d_str.parse()?;
625 }
626
627 if server != DEFAULT_USER_SERVER
628 && server != HIDDEN_USER_SERVER
629 && let Some((u, last_part)) = user.rsplit_once('.')
630 && let Ok(num_val) = last_part.parse::<u16>()
631 {
632 user = u;
633 agent = num_val as u8;
634 }
635
636 if let Some((u, last_part)) = user_part.rsplit_once('.')
637 && let Ok(num_val) = last_part.parse::<u16>()
638 {
639 if server == DEFAULT_USER_SERVER {
640 user = u;
641 device = num_val;
642 } else {
643 user = u;
644 if num_val > u8::MAX as u16 {
645 return Err(JidError::InvalidFormat(format!(
646 "Agent component out of range: {num_val}"
647 )));
648 }
649 agent = num_val as u8;
650 }
651 }
652
653 Ok(Jid {
654 user: user.to_string(),
655 server: cow_server_from_str(server),
656 agent,
657 device,
658 integrator: 0,
659 })
660 }
661}
662
663impl fmt::Display for Jid {
664 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
665 if self.user.is_empty() {
666 write!(f, "{}", self.server)
668 } else {
669 write!(f, "{}", self.user)?;
670
671 if self.agent > 0 {
676 let server_str = self.server(); if server_str != DEFAULT_USER_SERVER
681 && server_str != HIDDEN_USER_SERVER
682 && server_str != HOSTED_SERVER
683 {
684 write!(f, ".{}", self.agent)?;
685 }
686 }
687
688 if self.device > 0 {
689 write!(f, ":{}", self.device)?;
690 }
691
692 write!(f, "@{}", self.server)
693 }
694 }
695}
696
697impl<'a> fmt::Display for JidRef<'a> {
698 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
699 if self.user.is_empty() {
700 write!(f, "{}", self.server)
702 } else {
703 write!(f, "{}", self.user)?;
704
705 if self.agent > 0 {
710 let server_str = self.server(); if server_str != DEFAULT_USER_SERVER
715 && server_str != HIDDEN_USER_SERVER
716 && server_str != HOSTED_SERVER
717 {
718 write!(f, ".{}", self.agent)?;
719 }
720 }
721
722 if self.device > 0 {
723 write!(f, ":{}", self.device)?;
724 }
725
726 write!(f, "@{}", self.server)
727 }
728 }
729}
730
731impl From<Jid> for String {
732 fn from(jid: Jid) -> Self {
733 jid.to_string()
734 }
735}
736
737impl<'a> From<JidRef<'a>> for String {
738 fn from(jid: JidRef<'a>) -> Self {
739 jid.to_string()
740 }
741}
742
743impl TryFrom<String> for Jid {
744 type Error = JidError;
745 fn try_from(value: String) -> Result<Self, Self::Error> {
746 Jid::from_str(&value)
747 }
748}
749
750#[cfg(test)]
751mod tests {
752 use super::*;
753 use std::str::FromStr;
754
755 fn assert_jid_roundtrip(
757 input: &str,
758 expected_user: &str,
759 expected_server: &str,
760 expected_device: u16,
761 expected_agent: u8,
762 ) {
763 assert_jid_parse_and_display(
764 input,
765 expected_user,
766 expected_server,
767 expected_device,
768 expected_agent,
769 input,
770 );
771 }
772
773 fn assert_jid_parse_and_display(
775 input: &str,
776 expected_user: &str,
777 expected_server: &str,
778 expected_device: u16,
779 expected_agent: u8,
780 expected_output: &str,
781 ) {
782 let jid = Jid::from_str(input).unwrap_or_else(|_| panic!("Failed to parse JID: {}", input));
784
785 assert_eq!(
786 jid.user, expected_user,
787 "User part did not match for {}",
788 input
789 );
790 assert_eq!(
791 jid.server, expected_server,
792 "Server part did not match for {}",
793 input
794 );
795 assert_eq!(
796 jid.device, expected_device,
797 "Device part did not match for {}",
798 input
799 );
800 assert_eq!(
801 jid.agent, expected_agent,
802 "Agent part did not match for {}",
803 input
804 );
805
806 let formatted = jid.to_string();
808 assert_eq!(
809 formatted, expected_output,
810 "Formatted string did not match expected output for {}",
811 input
812 );
813 }
814
815 #[test]
816 fn test_jid_parsing_and_display_roundtrip() {
817 assert_jid_roundtrip(
819 "1234567890@s.whatsapp.net",
820 "1234567890",
821 "s.whatsapp.net",
822 0,
823 0,
824 );
825 assert_jid_roundtrip(
826 "1234567890:15@s.whatsapp.net",
827 "1234567890",
828 "s.whatsapp.net",
829 15,
830 0,
831 );
832 assert_jid_roundtrip("123-456@g.us", "123-456", "g.us", 0, 0);
833
834 assert_jid_roundtrip("s.whatsapp.net", "", "s.whatsapp.net", 0, 0);
837
838 assert_jid_roundtrip("12345.6789@lid", "12345.6789", "lid", 0, 0);
840 assert_jid_roundtrip("12345.6789:25@lid", "12345.6789", "lid", 25, 0);
841 }
842
843 #[test]
844 fn test_special_from_str_parsing() {
845 let jid = Jid::from_str("1234567890.2:15@hosted").expect("test hosted JID should be valid");
847 assert_eq!(jid.user, "1234567890");
848 assert_eq!(jid.server, "hosted");
849 assert_eq!(jid.device, 15);
850 assert_eq!(jid.agent, 2);
851 }
852
853 #[test]
854 fn test_manual_jid_formatting_edge_cases() {
855 let jid1 = Jid {
862 user: "1234567890".to_string(),
863 server: Cow::Borrowed("s.whatsapp.net"),
864 device: 15,
865 agent: 2, integrator: 0,
867 };
868 assert_eq!(jid1.to_string(), "1234567890:15@s.whatsapp.net");
871
872 let jid2 = Jid {
875 user: "12345.6789".to_string(),
876 server: Cow::Borrowed("lid"),
877 device: 25,
878 agent: 1, integrator: 0,
880 };
881 assert_eq!(jid2.to_string(), "12345.6789:25@lid");
884
885 let jid3 = Jid {
888 user: "1234567890".to_string(),
889 server: Cow::Borrowed("hosted"),
890 device: 15,
891 agent: 2,
892 integrator: 0,
893 };
894 assert_eq!(jid3.to_string(), "1234567890:15@hosted");
897
898 let jid4 = Jid {
900 user: "user".to_string(),
901 server: Cow::Owned("custom.net".to_string()),
902 device: 10,
903 agent: 5,
904 integrator: 0,
905 };
906 assert_eq!(jid4.to_string(), "user.5:10@custom.net");
908 }
909
910 #[test]
911 fn test_invalid_jids_should_fail_to_parse() {
912 assert!(Jid::from_str("thisisnotajid").is_err());
913 assert!(Jid::from_str("").is_err());
914 assert!(Jid::from_str("@s.whatsapp.net").is_ok());
916 assert!(Jid::from_str("@unknown.server").is_err());
918 assert!(Jid::from_str("2").is_err());
921 }
922
923 #[test]
951 fn test_is_hosted_device_detection() {
952 let cloud_api_device: Jid = "5511999887766:99@s.whatsapp.net"
957 .parse()
958 .expect("test JID should be valid");
959 assert!(
960 cloud_api_device.is_hosted(),
961 "Device ID 99 on s.whatsapp.net should be detected as hosted (Cloud API)"
962 );
963
964 let cloud_api_lid: Jid = "100000012345678:99@lid"
966 .parse()
967 .expect("test JID should be valid");
968 assert!(
969 cloud_api_lid.is_hosted(),
970 "Device ID 99 on lid server should be detected as hosted"
971 );
972
973 let hosted_server: Jid = "5511999887766:99@hosted"
975 .parse()
976 .expect("test JID should be valid");
977 assert!(
978 hosted_server.is_hosted(),
979 "JID with @hosted server should be detected as hosted"
980 );
981
982 let hosted_lid_server: Jid = "100000012345678:99@hosted.lid"
984 .parse()
985 .expect("test JID should be valid");
986 assert!(
987 hosted_lid_server.is_hosted(),
988 "JID with @hosted.lid server should be detected as hosted"
989 );
990
991 let hosted_server_other_device: Jid = "5511999887766:0@hosted"
994 .parse()
995 .expect("test JID should be valid");
996 assert!(
997 hosted_server_other_device.is_hosted(),
998 "JID with @hosted server should be hosted regardless of device ID"
999 );
1000
1001 let regular_phone: Jid = "5511999887766:0@s.whatsapp.net"
1005 .parse()
1006 .expect("test JID should be valid");
1007 assert!(
1008 !regular_phone.is_hosted(),
1009 "Regular phone device (ID 0) should NOT be hosted"
1010 );
1011
1012 let companion_device: Jid = "5511999887766:33@s.whatsapp.net"
1014 .parse()
1015 .expect("test JID should be valid");
1016 assert!(
1017 !companion_device.is_hosted(),
1018 "Companion device (ID 33) should NOT be hosted"
1019 );
1020
1021 let regular_lid: Jid = "100000012345678:0@lid"
1023 .parse()
1024 .expect("test JID should be valid");
1025 assert!(
1026 !regular_lid.is_hosted(),
1027 "Regular LID device should NOT be hosted"
1028 );
1029
1030 let lid_companion: Jid = "100000012345678:33@lid"
1032 .parse()
1033 .expect("test JID should be valid");
1034 assert!(
1035 !lid_companion.is_hosted(),
1036 "LID companion device (ID 33) should NOT be hosted"
1037 );
1038
1039 let group_jid: Jid = "120363012345678@g.us"
1041 .parse()
1042 .expect("test JID should be valid");
1043 assert!(
1044 !group_jid.is_hosted(),
1045 "Group JID should NOT be detected as hosted"
1046 );
1047
1048 let user_jid: Jid = "5511999887766@s.whatsapp.net"
1050 .parse()
1051 .expect("test JID should be valid");
1052 assert!(
1053 !user_jid.is_hosted(),
1054 "User JID without device should NOT be hosted"
1055 );
1056
1057 let bot_jid: Jid = "13136555001:0@s.whatsapp.net"
1059 .parse()
1060 .expect("test JID should be valid");
1061 assert!(
1062 !bot_jid.is_hosted(),
1063 "Bot JID should NOT be detected as hosted (different mechanism)"
1064 );
1065 }
1066
1067 #[test]
1078 fn test_hosted_device_filtering_for_groups() {
1079 let devices: Vec<Jid> = vec![
1081 "5511999887766:0@s.whatsapp.net"
1083 .parse()
1084 .expect("test JID should be valid"), "5511999887766:33@s.whatsapp.net"
1086 .parse()
1087 .expect("test JID should be valid"), "5521988776655:0@s.whatsapp.net"
1089 .parse()
1090 .expect("test JID should be valid"), "100000012345678:0@lid"
1092 .parse()
1093 .expect("test JID should be valid"), "100000012345678:33@lid"
1095 .parse()
1096 .expect("test JID should be valid"), "5531977665544:99@s.whatsapp.net"
1099 .parse()
1100 .expect("test JID should be valid"), "100000087654321:99@lid"
1102 .parse()
1103 .expect("test JID should be valid"), "5541966554433:99@hosted"
1105 .parse()
1106 .expect("test JID should be valid"), ];
1108
1109 let filtered: Vec<&Jid> = devices.iter().filter(|jid| !jid.is_hosted()).collect();
1111
1112 assert_eq!(
1114 filtered.len(),
1115 5,
1116 "Should have 5 non-hosted devices after filtering"
1117 );
1118
1119 for jid in &filtered {
1121 assert!(
1122 !jid.is_hosted(),
1123 "Filtered list should not contain hosted devices: {}",
1124 jid
1125 );
1126 }
1127
1128 let hosted_count = devices.iter().filter(|jid| jid.is_hosted()).count();
1130 assert_eq!(hosted_count, 3, "Should have filtered out 3 hosted devices");
1131 }
1132
1133 #[test]
1134 fn test_jid_pn_factory() {
1135 let jid = Jid::pn("1234567890");
1136 assert_eq!(jid.user, "1234567890");
1137 assert_eq!(jid.server, DEFAULT_USER_SERVER);
1138 assert_eq!(jid.device, 0);
1139 assert!(jid.is_pn());
1140 }
1141
1142 #[test]
1143 fn test_jid_lid_factory() {
1144 let jid = Jid::lid("100000012345678");
1145 assert_eq!(jid.user, "100000012345678");
1146 assert_eq!(jid.server, HIDDEN_USER_SERVER);
1147 assert_eq!(jid.device, 0);
1148 assert!(jid.is_lid());
1149 }
1150
1151 #[test]
1152 fn test_jid_group_factory() {
1153 let jid = Jid::group("123456789-1234567890");
1154 assert_eq!(jid.user, "123456789-1234567890");
1155 assert_eq!(jid.server, GROUP_SERVER);
1156 assert!(jid.is_group());
1157 }
1158
1159 #[test]
1160 fn test_jid_pn_device_factory() {
1161 let jid = Jid::pn_device("1234567890", 5);
1162 assert_eq!(jid.user, "1234567890");
1163 assert_eq!(jid.server, DEFAULT_USER_SERVER);
1164 assert_eq!(jid.device, 5);
1165 assert!(jid.is_pn());
1166 assert!(jid.is_ad());
1167 }
1168
1169 #[test]
1170 fn test_jid_lid_device_factory() {
1171 let jid = Jid::lid_device("100000012345678", 33);
1172 assert_eq!(jid.user, "100000012345678");
1173 assert_eq!(jid.server, HIDDEN_USER_SERVER);
1174 assert_eq!(jid.device, 33);
1175 assert!(jid.is_lid());
1176 assert!(jid.is_ad());
1177 }
1178
1179 #[test]
1180 fn test_status_broadcast_jid() {
1181 let jid = Jid::status_broadcast();
1182 assert_eq!(jid.user, STATUS_BROADCAST_USER);
1183 assert_eq!(jid.server, BROADCAST_SERVER);
1184 assert_eq!(jid.device, 0);
1185 assert!(jid.is_status_broadcast());
1186 assert!(!jid.is_group());
1187 assert!(!jid.is_broadcast_list());
1188 assert_eq!(jid.to_string(), "status@broadcast");
1189
1190 let parsed: Jid = "status@broadcast".parse().expect("should parse");
1192 assert!(parsed.is_status_broadcast());
1193 assert_eq!(parsed.user, "status");
1194 assert_eq!(parsed.server, "broadcast");
1195
1196 let broadcast_list = Jid::new("12345", BROADCAST_SERVER);
1198 assert!(broadcast_list.is_broadcast_list());
1199 assert!(!broadcast_list.is_status_broadcast());
1200 }
1201
1202 #[test]
1203 fn test_jid_to_non_ad_preserves_user_server() {
1204 let device_jid = Jid::pn_device("1234567890", 33);
1206 let non_ad = device_jid.to_non_ad();
1207 assert_eq!(non_ad.user, "1234567890");
1208 assert_eq!(non_ad.server, DEFAULT_USER_SERVER);
1209 assert_eq!(non_ad.device, 0);
1210 assert!(!non_ad.is_ad());
1211
1212 let lid_device = Jid::lid_device("100000012345678", 25);
1214 let lid_non_ad = lid_device.to_non_ad();
1215 assert_eq!(lid_non_ad.user, "100000012345678");
1216 assert_eq!(lid_non_ad.server, HIDDEN_USER_SERVER);
1217 assert_eq!(lid_non_ad.device, 0);
1218
1219 let status = Jid::status_broadcast();
1221 let status_non_ad = status.to_non_ad();
1222 assert_eq!(status_non_ad.to_string(), "status@broadcast");
1223 }
1224
1225 #[test]
1226 fn test_jid_factories_with_string_types() {
1227 let jid1 = Jid::pn("123");
1229 assert_eq!(jid1.user, "123");
1230
1231 let jid2 = Jid::lid(String::from("456"));
1233 assert_eq!(jid2.user, "456");
1234
1235 let user = "789".to_string();
1237 let jid3 = Jid::group(user);
1238 assert_eq!(jid3.user, "789");
1239 }
1240}