1use plist::Value;
7
8use crate::{
9 error::plist::PlistParseError,
10 message_types::variants::BalloonProvider,
11 tables::{
12 messages::{
13 body::{parse_body_legacy, parse_body_typedstream},
14 models::BubbleComponent,
15 },
16 table::AttributedBody,
17 },
18 util::{
19 dates::TIMESTAMP_FACTOR,
20 plist::{
21 extract_array_key, extract_bytes_key, extract_dictionary, extract_int_key,
22 plist_as_dictionary,
23 },
24 streamtyped::parse,
25 typedstream::{models::Archivable, parser::TypedStreamReader},
26 },
27};
28
29#[derive(Debug, PartialEq, Eq)]
31pub enum EditStatus {
32 Edited,
34 Unsent,
36 Original,
38}
39
40#[derive(Debug, PartialEq)]
42pub struct EditedEvent {
43 pub date: i64,
45 pub text: Option<String>,
47 pub components: Option<Vec<Archivable>>,
49 pub guid: Option<String>,
51}
52
53impl EditedEvent {
54 pub(crate) fn new(
55 date: i64,
56 text: Option<String>,
57 components: Option<Vec<Archivable>>,
58 guid: Option<String>,
59 ) -> Self {
60 Self {
61 date,
62 text,
63 components,
64 guid,
65 }
66 }
67}
68
69impl AttributedBody for EditedEvent {
70 fn body(&self) -> Vec<BubbleComponent> {
74 if let Some(body) =
75 parse_body_typedstream(self.components.as_ref(), self.text.as_deref(), None)
76 {
77 return body;
78 }
79 parse_body_legacy(&self.text)
80 }
81}
82
83#[derive(Debug, PartialEq)]
85pub struct EditedMessagePart {
86 pub status: EditStatus,
88 pub edit_history: Vec<EditedEvent>,
90}
91
92impl Default for EditedMessagePart {
93 fn default() -> Self {
94 Self {
95 status: EditStatus::Original,
96 edit_history: vec![],
97 }
98 }
99}
100
101#[derive(Debug, PartialEq)]
131pub struct EditedMessage {
132 pub parts: Vec<EditedMessagePart>,
134}
135
136impl<'a> BalloonProvider<'a> for EditedMessage {
137 fn from_map(payload: &'a Value) -> Result<Self, PlistParseError> {
138 let plist_root = plist_as_dictionary(payload)?;
140
141 let message_parts = extract_dictionary(plist_root, "otr")?;
143
144 let mut edited = Self::with_capacity(message_parts.len());
146 message_parts
147 .values()
148 .for_each(|_| edited.parts.push(EditedMessagePart::default()));
149
150 if let Ok(edited_message_events) = extract_dictionary(plist_root, "ec") {
151 for (idx, (key, events)) in edited_message_events.iter().enumerate() {
152 let events = events
153 .as_array()
154 .ok_or_else(|| PlistParseError::InvalidTypeIndex(idx, "array".to_string()))?;
155 let parsed_key = key.parse::<usize>().map_err(|_| {
156 PlistParseError::InvalidType(key.to_string(), "string".to_string())
157 })?;
158
159 for event in events {
160 let message_data = event.as_dictionary().ok_or_else(|| {
161 PlistParseError::InvalidTypeIndex(idx, "dictionary".to_string())
162 })?;
163
164 let timestamp = extract_int_key(message_data, "d")? * TIMESTAMP_FACTOR;
165
166 let typedstream = extract_bytes_key(message_data, "t")?;
167
168 let mut parser = TypedStreamReader::from(typedstream);
169
170 let components = parser.parse();
171
172 let text = components
173 .as_ref()
174 .ok()
175 .and_then(|items| items.first())
176 .and_then(|item| item.as_nsstring())
177 .map(String::from)
178 .or_else(|| parse(typedstream.to_vec()).ok());
179
180 let guid = message_data
181 .get("bcg")
182 .and_then(|item| item.as_string())
183 .map(Into::into);
184
185 if let Some(item) = edited.parts.get_mut(parsed_key) {
186 item.status = EditStatus::Edited;
187 item.edit_history.push(EditedEvent::new(
188 timestamp,
189 text,
190 components.ok(),
191 guid,
192 ));
193 }
194 }
195 }
196 }
197
198 if let Ok(unsent_message_indexes) = extract_array_key(plist_root, "rp") {
199 for (idx, unsent_message_idx) in unsent_message_indexes.iter().enumerate() {
200 let parsed_idx = unsent_message_idx
201 .as_signed_integer()
202 .ok_or_else(|| PlistParseError::InvalidTypeIndex(idx, "int".to_string()))?
203 as usize;
204 if let Some(item) = edited.parts.get_mut(parsed_idx) {
205 item.status = EditStatus::Unsent;
206 }
207 }
208 }
209
210 Ok(edited)
211 }
212}
213
214impl EditedMessage {
215 fn with_capacity(capacity: usize) -> Self {
217 EditedMessage {
218 parts: Vec::with_capacity(capacity),
219 }
220 }
221
222 #[must_use]
224 pub fn part(&self, index: usize) -> Option<&EditedMessagePart> {
225 self.parts.get(index)
226 }
227
228 #[must_use]
230 pub fn is_unedited_at(&self, index: usize) -> bool {
231 match self.parts.get(index) {
232 Some(part) => matches!(part.status, EditStatus::Original),
233 None => false,
234 }
235 }
236
237 #[must_use]
239 pub fn items(&self) -> usize {
240 self.parts.len()
241 }
242}
243
244#[cfg(test)]
245mod test_parser {
246 use crate::message_types::edited::{EditStatus, EditedEvent, EditedMessagePart};
247 use crate::message_types::{edited::EditedMessage, variants::BalloonProvider};
248 use crate::util::typedstream::models::{Archivable, Class, OutputData};
249 use plist::Value;
250 use std::env::current_dir;
251 use std::fs::File;
252
253 #[test]
254 fn test_parse_edited() {
255 let plist_path = current_dir()
256 .unwrap()
257 .as_path()
258 .join("test_data/edited_message/Edited.plist");
259 let plist_data = File::open(plist_path).unwrap();
260 let plist = Value::from_reader(plist_data).unwrap();
261 let parsed = EditedMessage::from_map(&plist).unwrap();
262
263 let expected = EditedMessage {
264 parts: vec![EditedMessagePart {
265 status: EditStatus::Edited,
266 edit_history: vec![
267 EditedEvent::new(
268 690513474000000000,
269 Some("First message ".to_string()),
270 Some(vec![
271 Archivable::Object(
272 Class {
273 name: "NSString".to_string(),
274 version: 1,
275 },
276 vec![OutputData::String("First message ".to_string())],
277 ),
278 Archivable::Data(vec![
279 OutputData::SignedInteger(1),
280 OutputData::UnsignedInteger(15),
281 ]),
282 Archivable::Object(
283 Class {
284 name: "NSDictionary".to_string(),
285 version: 0,
286 },
287 vec![OutputData::SignedInteger(1)],
288 ),
289 Archivable::Object(
290 Class {
291 name: "NSString".to_string(),
292 version: 1,
293 },
294 vec![OutputData::String(
295 "__kIMMessagePartAttributeName".to_string(),
296 )],
297 ),
298 Archivable::Object(
299 Class {
300 name: "NSNumber".to_string(),
301 version: 0,
302 },
303 vec![OutputData::SignedInteger(0)],
304 ),
305 ]),
306 None,
307 ),
308 EditedEvent::new(
309 690513480000000000,
310 Some("Edit 1".to_string()),
311 Some(vec![
312 Archivable::Object(
313 Class {
314 name: "NSString".to_string(),
315 version: 1,
316 },
317 vec![OutputData::String("Edit 1".to_string())],
318 ),
319 Archivable::Data(vec![
320 OutputData::SignedInteger(1),
321 OutputData::UnsignedInteger(6),
322 ]),
323 Archivable::Object(
324 Class {
325 name: "NSDictionary".to_string(),
326 version: 0,
327 },
328 vec![OutputData::SignedInteger(2)],
329 ),
330 Archivable::Object(
331 Class {
332 name: "NSString".to_string(),
333 version: 1,
334 },
335 vec![OutputData::String(
336 "__kIMBaseWritingDirectionAttributeName".to_string(),
337 )],
338 ),
339 Archivable::Object(
340 Class {
341 name: "NSNumber".to_string(),
342 version: 0,
343 },
344 vec![OutputData::SignedInteger(-1)],
345 ),
346 Archivable::Object(
347 Class {
348 name: "NSString".to_string(),
349 version: 1,
350 },
351 vec![OutputData::String(
352 "__kIMMessagePartAttributeName".to_string(),
353 )],
354 ),
355 Archivable::Object(
356 Class {
357 name: "NSNumber".to_string(),
358 version: 0,
359 },
360 vec![OutputData::SignedInteger(0)],
361 ),
362 ]),
363 None,
364 ),
365 EditedEvent::new(
366 690513485000000000,
367 Some("Edit 2".to_string()),
368 Some(vec![
369 Archivable::Object(
370 Class {
371 name: "NSString".to_string(),
372 version: 1,
373 },
374 vec![OutputData::String("Edit 2".to_string())],
375 ),
376 Archivable::Data(vec![
377 OutputData::SignedInteger(1),
378 OutputData::UnsignedInteger(6),
379 ]),
380 Archivable::Object(
381 Class {
382 name: "NSDictionary".to_string(),
383 version: 0,
384 },
385 vec![OutputData::SignedInteger(2)],
386 ),
387 Archivable::Object(
388 Class {
389 name: "NSString".to_string(),
390 version: 1,
391 },
392 vec![OutputData::String(
393 "__kIMBaseWritingDirectionAttributeName".to_string(),
394 )],
395 ),
396 Archivable::Object(
397 Class {
398 name: "NSNumber".to_string(),
399 version: 0,
400 },
401 vec![OutputData::SignedInteger(-1)],
402 ),
403 Archivable::Object(
404 Class {
405 name: "NSString".to_string(),
406 version: 1,
407 },
408 vec![OutputData::String(
409 "__kIMMessagePartAttributeName".to_string(),
410 )],
411 ),
412 Archivable::Object(
413 Class {
414 name: "NSNumber".to_string(),
415 version: 0,
416 },
417 vec![OutputData::SignedInteger(0)],
418 ),
419 ]),
420 None,
421 ),
422 EditedEvent::new(
423 690513494000000000,
424 Some("Edited message".to_string()),
425 Some(vec![
426 Archivable::Object(
427 Class {
428 name: "NSString".to_string(),
429 version: 1,
430 },
431 vec![OutputData::String("Edited message".to_string())],
432 ),
433 Archivable::Data(vec![
434 OutputData::SignedInteger(1),
435 OutputData::UnsignedInteger(14),
436 ]),
437 Archivable::Object(
438 Class {
439 name: "NSDictionary".to_string(),
440 version: 0,
441 },
442 vec![OutputData::SignedInteger(2)],
443 ),
444 Archivable::Object(
445 Class {
446 name: "NSString".to_string(),
447 version: 1,
448 },
449 vec![OutputData::String(
450 "__kIMBaseWritingDirectionAttributeName".to_string(),
451 )],
452 ),
453 Archivable::Object(
454 Class {
455 name: "NSNumber".to_string(),
456 version: 0,
457 },
458 vec![OutputData::SignedInteger(-1)],
459 ),
460 Archivable::Object(
461 Class {
462 name: "NSString".to_string(),
463 version: 1,
464 },
465 vec![OutputData::String(
466 "__kIMMessagePartAttributeName".to_string(),
467 )],
468 ),
469 Archivable::Object(
470 Class {
471 name: "NSNumber".to_string(),
472 version: 0,
473 },
474 vec![OutputData::SignedInteger(0)],
475 ),
476 ]),
477 None,
478 ),
479 ],
480 }],
481 };
482
483 assert_eq!(parsed, expected);
484
485 let expected_item = Some(expected.parts.first().unwrap());
486 assert_eq!(parsed.part(0), expected_item);
487 }
488
489 #[test]
490 fn test_parse_edited_to_link() {
491 let plist_path = current_dir()
492 .unwrap()
493 .as_path()
494 .join("test_data/edited_message/EditedToLink.plist");
495 let plist_data = File::open(plist_path).unwrap();
496 let plist = Value::from_reader(plist_data).unwrap();
497 let parsed = EditedMessage::from_map(&plist).unwrap();
498
499 let expected = EditedMessage {
500 parts: vec![
501 EditedMessagePart {
502 status: EditStatus::Original,
503 edit_history: vec![], },
505 EditedMessagePart {
506 status: EditStatus::Edited,
507 edit_history: vec![
508 EditedEvent::new(
509 690514004000000000,
510 Some("here we go!".to_string()),
511 Some(vec![
512 Archivable::Object(
513 Class {
514 name: "NSString".to_string(),
515 version: 1,
516 },
517 vec![OutputData::String("here we go!".to_string())],
518 ),
519 Archivable::Data(vec![
520 OutputData::SignedInteger(1),
521 OutputData::UnsignedInteger(11),
522 ]),
523 Archivable::Object(
524 Class {
525 name: "NSDictionary".to_string(),
526 version: 0,
527 },
528 vec![OutputData::SignedInteger(1)],
529 ),
530 Archivable::Object(
531 Class {
532 name: "NSString".to_string(),
533 version: 1,
534 },
535 vec![OutputData::String(
536 "__kIMMessagePartAttributeName".to_string(),
537 )],
538 ),
539 Archivable::Object(
540 Class {
541 name: "NSNumber".to_string(),
542 version: 0,
543 },
544 vec![OutputData::SignedInteger(1)],
545 ),
546 ]),
547 None,
548 ),
549 EditedEvent::new(
550 690514772000000000,
551 Some(
552 "https://github.com/ReagentX/imessage-exporter/issues/10"
553 .to_string(),
554 ),
555 Some(vec![
556 Archivable::Object(
557 Class {
558 name: "NSString".to_string(),
559 version: 1,
560 },
561 vec![OutputData::String(
562 "https://github.com/ReagentX/imessage-exporter/issues/10"
563 .to_string(),
564 )],
565 ),
566 Archivable::Data(vec![
567 OutputData::SignedInteger(1),
568 OutputData::UnsignedInteger(55),
569 ]),
570 Archivable::Object(
571 Class {
572 name: "NSDictionary".to_string(),
573 version: 0,
574 },
575 vec![OutputData::SignedInteger(1)],
576 ),
577 Archivable::Object(
578 Class {
579 name: "NSString".to_string(),
580 version: 1,
581 },
582 vec![OutputData::String(
583 "__kIMMessagePartAttributeName".to_string(),
584 )],
585 ),
586 Archivable::Object(
587 Class {
588 name: "NSNumber".to_string(),
589 version: 0,
590 },
591 vec![OutputData::SignedInteger(1)],
592 ),
593 ]),
594 Some("292BF9C6-C9B8-4827-BE65-6EA1C9B5B384".to_string()),
595 ),
596 ],
597 },
598 ],
599 };
600
601 assert_eq!(parsed, expected);
602 }
603
604 #[test]
605 fn test_parse_edited_to_link_and_back() {
606 let plist_path = current_dir()
607 .unwrap()
608 .as_path()
609 .join("test_data/edited_message/EditedToLinkAndBack.plist");
610 let plist_data = File::open(plist_path).unwrap();
611 let plist = Value::from_reader(plist_data).unwrap();
612 let parsed = EditedMessage::from_map(&plist).unwrap();
613
614 let expected = EditedMessage {
615 parts: vec![EditedMessagePart {
616 status: EditStatus::Edited,
617 edit_history: vec![
618 EditedEvent::new(
619 690514809000000000,
620 Some("This is a normal message".to_string()),
621 Some(vec![
622 Archivable::Object(
623 Class {
624 name: "NSString".to_string(),
625 version: 1,
626 },
627 vec![OutputData::String("This is a normal message".to_string())],
628 ),
629 Archivable::Data(vec![
630 OutputData::SignedInteger(1),
631 OutputData::UnsignedInteger(24),
632 ]),
633 Archivable::Object(
634 Class {
635 name: "NSDictionary".to_string(),
636 version: 0,
637 },
638 vec![OutputData::SignedInteger(1)],
639 ),
640 Archivable::Object(
641 Class {
642 name: "NSString".to_string(),
643 version: 1,
644 },
645 vec![OutputData::String(
646 "__kIMMessagePartAttributeName".to_string(),
647 )],
648 ),
649 Archivable::Object(
650 Class {
651 name: "NSNumber".to_string(),
652 version: 0,
653 },
654 vec![OutputData::SignedInteger(0)],
655 ),
656 ]),
657 None,
658 ),
659 EditedEvent::new(
660 690514819000000000,
661 Some("Edit to a url https://github.com/ReagentX/imessage-exporter/issues/10"
662 .to_string()),
663 Some(vec![
664 Archivable::Object(
665 Class {
666 name: "NSString".to_string(),
667 version: 1,
668 },
669 vec![OutputData::String("Edit to a url https://github.com/ReagentX/imessage-exporter/issues/10".to_string())],
670 ),
671 Archivable::Data(vec![
672 OutputData::SignedInteger(1),
673 OutputData::UnsignedInteger(69),
674 ]),
675 Archivable::Object(
676 Class {
677 name: "NSDictionary".to_string(),
678 version: 0,
679 },
680 vec![OutputData::SignedInteger(1)],
681 ),
682 Archivable::Object(
683 Class {
684 name: "NSString".to_string(),
685 version: 1,
686 },
687 vec![OutputData::String(
688 "__kIMMessagePartAttributeName".to_string(),
689 )],
690 ),
691 Archivable::Object(
692 Class {
693 name: "NSNumber".to_string(),
694 version: 0,
695 },
696 vec![OutputData::SignedInteger(0)],
697 ),
698 ]),
699 Some("0B9103FE-280C-4BD0-A66F-4EDEE3443247".to_string()),
700 ),
701 EditedEvent::new(
702 690514834000000000,
703 Some("And edit it back to a normal message...".to_string()),
704 Some(vec![
705 Archivable::Object(
706 Class {
707 name: "NSString".to_string(),
708 version: 1,
709 },
710 vec![OutputData::String("And edit it back to a normal message...".to_string())],
711 ),
712 Archivable::Data(vec![
713 OutputData::SignedInteger(1),
714 OutputData::UnsignedInteger(39),
715 ]),
716 Archivable::Object(
717 Class {
718 name: "NSDictionary".to_string(),
719 version: 0,
720 },
721 vec![OutputData::SignedInteger(1)],
722 ),
723 Archivable::Object(
724 Class {
725 name: "NSString".to_string(),
726 version: 1,
727 },
728 vec![OutputData::String(
729 "__kIMMessagePartAttributeName".to_string(),
730 )],
731 ),
732 Archivable::Object(
733 Class {
734 name: "NSNumber".to_string(),
735 version: 0,
736 },
737 vec![OutputData::SignedInteger(0)],
738 ),
739 ]),
740 Some("0D93DF88-05BA-4418-9B20-79918ADD9923".to_string()),
741 ),
742 ],
743 }],
744 };
745
746 assert_eq!(parsed, expected);
747 }
748
749 #[test]
750 fn test_parse_deleted() {
751 let plist_path = current_dir()
752 .unwrap()
753 .as_path()
754 .join("test_data/edited_message/Deleted.plist");
755 let plist_data = File::open(plist_path).unwrap();
756 let plist = Value::from_reader(plist_data).unwrap();
757 let parsed = EditedMessage::from_map(&plist).unwrap();
758
759 let expected = EditedMessage {
760 parts: vec![EditedMessagePart {
761 status: EditStatus::Unsent,
762 edit_history: vec![],
763 }],
764 };
765
766 assert_eq!(parsed, expected);
767 }
768
769 #[test]
770 fn test_parse_multipart_deleted() {
771 let plist_path = current_dir()
772 .unwrap()
773 .as_path()
774 .join("test_data/edited_message/MultiPartOneDeleted.plist");
775 let plist_data = File::open(plist_path).unwrap();
776 let plist = Value::from_reader(plist_data).unwrap();
777 let parsed = EditedMessage::from_map(&plist).unwrap();
778
779 let expected = EditedMessage {
780 parts: vec![
781 EditedMessagePart {
782 status: EditStatus::Original,
783 edit_history: vec![],
784 },
785 EditedMessagePart {
786 status: EditStatus::Original,
787 edit_history: vec![],
788 },
789 EditedMessagePart {
790 status: EditStatus::Original,
791 edit_history: vec![],
792 },
793 EditedMessagePart {
794 status: EditStatus::Unsent,
795 edit_history: vec![],
796 },
797 ],
798 };
799
800 assert_eq!(parsed, expected);
801 }
802
803 #[test]
804 fn test_parse_multipart_edited_and_deleted() {
805 let plist_path = current_dir()
806 .unwrap()
807 .as_path()
808 .join("test_data/edited_message/EditedAndDeleted.plist");
809 let plist_data = File::open(plist_path).unwrap();
810 let plist = Value::from_reader(plist_data).unwrap();
811 let parsed = EditedMessage::from_map(&plist).unwrap();
812
813 let expected = EditedMessage {
814 parts: vec![
815 EditedMessagePart {
816 status: EditStatus::Original,
817 edit_history: vec![],
818 },
819 EditedMessagePart {
820 status: EditStatus::Original,
821 edit_history: vec![],
822 },
823 EditedMessagePart {
824 status: EditStatus::Edited,
825 edit_history: vec![
826 EditedEvent::new(
827 743907180000000000,
828 Some("Second message".to_string()),
829 Some(vec![
830 Archivable::Object(
831 Class {
832 name: "NSString".to_string(),
833 version: 1,
834 },
835 vec![OutputData::String("Second message".to_string())],
836 ),
837 Archivable::Data(vec![
838 OutputData::SignedInteger(1),
839 OutputData::UnsignedInteger(14),
840 ]),
841 Archivable::Object(
842 Class {
843 name: "NSDictionary".to_string(),
844 version: 0,
845 },
846 vec![OutputData::SignedInteger(1)],
847 ),
848 Archivable::Object(
849 Class {
850 name: "NSString".to_string(),
851 version: 1,
852 },
853 vec![OutputData::String(
854 "__kIMMessagePartAttributeName".to_string(),
855 )],
856 ),
857 Archivable::Object(
858 Class {
859 name: "NSNumber".to_string(),
860 version: 0,
861 },
862 vec![OutputData::SignedInteger(2)],
863 ),
864 ]),
865 None,
866 ),
867 EditedEvent::new(
868 743907190000000000,
869 Some("Second message got edited!".to_string()),
870 Some(vec![
871 Archivable::Object(
872 Class {
873 name: "NSString".to_string(),
874 version: 1,
875 },
876 vec![OutputData::String(
877 "Second message got edited!".to_string(),
878 )],
879 ),
880 Archivable::Data(vec![
881 OutputData::SignedInteger(1),
882 OutputData::UnsignedInteger(26),
883 ]),
884 Archivable::Object(
885 Class {
886 name: "NSDictionary".to_string(),
887 version: 0,
888 },
889 vec![OutputData::SignedInteger(1)],
890 ),
891 Archivable::Object(
892 Class {
893 name: "NSString".to_string(),
894 version: 1,
895 },
896 vec![OutputData::String(
897 "__kIMMessagePartAttributeName".to_string(),
898 )],
899 ),
900 Archivable::Object(
901 Class {
902 name: "NSNumber".to_string(),
903 version: 0,
904 },
905 vec![OutputData::SignedInteger(2)],
906 ),
907 ]),
908 None,
909 ),
910 ],
911 },
912 ],
913 };
914
915 assert_eq!(parsed, expected);
916 }
917
918 #[test]
919 fn test_parse_multipart_edited_and_unsent() {
920 let plist_path = current_dir()
921 .unwrap()
922 .as_path()
923 .join("test_data/edited_message/EditedAndUnsent.plist");
924 let plist_data = File::open(plist_path).unwrap();
925 let plist = Value::from_reader(plist_data).unwrap();
926 let parsed = EditedMessage::from_map(&plist).unwrap();
927
928 let expected = EditedMessage {
929 parts: vec![
930 EditedMessagePart {
931 status: EditStatus::Original,
932 edit_history: vec![],
933 },
934 EditedMessagePart {
935 status: EditStatus::Original,
936 edit_history: vec![],
937 },
938 EditedMessagePart {
939 status: EditStatus::Edited,
940 edit_history: vec![
941 EditedEvent::new(
942 743907435000000000,
943 Some("Second test".to_string()),
944 Some(vec![
945 Archivable::Object(
946 Class {
947 name: "NSString".to_string(),
948 version: 1,
949 },
950 vec![OutputData::String("Second test".to_string())],
951 ),
952 Archivable::Data(vec![
953 OutputData::SignedInteger(1),
954 OutputData::UnsignedInteger(11),
955 ]),
956 Archivable::Object(
957 Class {
958 name: "NSDictionary".to_string(),
959 version: 0,
960 },
961 vec![OutputData::SignedInteger(1)],
962 ),
963 Archivable::Object(
964 Class {
965 name: "NSString".to_string(),
966 version: 1,
967 },
968 vec![OutputData::String(
969 "__kIMMessagePartAttributeName".to_string(),
970 )],
971 ),
972 Archivable::Object(
973 Class {
974 name: "NSNumber".to_string(),
975 version: 0,
976 },
977 vec![OutputData::SignedInteger(2)],
978 ),
979 ]),
980 None,
981 ),
982 EditedEvent::new(
983 743907448000000000,
984 Some("Second test was edited!".to_string()),
985 Some(vec![
986 Archivable::Object(
987 Class {
988 name: "NSString".to_string(),
989 version: 1,
990 },
991 vec![OutputData::String("Second test was edited!".to_string())],
992 ),
993 Archivable::Data(vec![
994 OutputData::SignedInteger(1),
995 OutputData::UnsignedInteger(23),
996 ]),
997 Archivable::Object(
998 Class {
999 name: "NSDictionary".to_string(),
1000 version: 0,
1001 },
1002 vec![OutputData::SignedInteger(1)],
1003 ),
1004 Archivable::Object(
1005 Class {
1006 name: "NSString".to_string(),
1007 version: 1,
1008 },
1009 vec![OutputData::String(
1010 "__kIMMessagePartAttributeName".to_string(),
1011 )],
1012 ),
1013 Archivable::Object(
1014 Class {
1015 name: "NSNumber".to_string(),
1016 version: 0,
1017 },
1018 vec![OutputData::SignedInteger(2)],
1019 ),
1020 ]),
1021 None,
1022 ),
1023 ],
1024 },
1025 EditedMessagePart {
1026 status: EditStatus::Unsent,
1027 edit_history: vec![],
1028 },
1029 ],
1030 };
1031
1032 assert_eq!(parsed, expected);
1033 }
1034
1035 #[test]
1036 fn test_parse_edited_with_formatting() {
1037 let plist_path = current_dir()
1038 .unwrap()
1039 .as_path()
1040 .join("test_data/edited_message/EditedWithFormatting.plist");
1041 let plist_data = File::open(plist_path).unwrap();
1042 let plist = Value::from_reader(plist_data).unwrap();
1043 let parsed = EditedMessage::from_map(&plist).unwrap();
1044
1045 let expected = EditedMessage {
1046 parts: vec![EditedMessagePart {
1047 status: EditStatus::Edited,
1048 edit_history: vec![
1049 EditedEvent::new(
1050 758573156000000000,
1051 Some("Test".to_string()),
1052 Some(vec![
1053 Archivable::Object(
1054 Class {
1055 name: "NSString".to_string(),
1056 version: 1,
1057 },
1058 vec![OutputData::String("Test".to_string())],
1059 ),
1060 Archivable::Data(vec![
1061 OutputData::SignedInteger(1),
1062 OutputData::UnsignedInteger(4),
1063 ]),
1064 Archivable::Object(
1065 Class {
1066 name: "NSDictionary".to_string(),
1067 version: 0,
1068 },
1069 vec![OutputData::SignedInteger(1)],
1070 ),
1071 Archivable::Object(
1072 Class {
1073 name: "NSString".to_string(),
1074 version: 1,
1075 },
1076 vec![OutputData::String(
1077 "__kIMMessagePartAttributeName".to_string(),
1078 )],
1079 ),
1080 Archivable::Object(
1081 Class {
1082 name: "NSNumber".to_string(),
1083 version: 0,
1084 },
1085 vec![OutputData::SignedInteger(0)],
1086 ),
1087 ]),
1088 None,
1089 ),
1090 EditedEvent::new(
1091 758573166000000000,
1092 Some("Test".to_string()),
1093 Some(vec![
1094 Archivable::Object(
1095 Class {
1096 name: "NSString".to_string(),
1097 version: 1,
1098 },
1099 vec![OutputData::String("Test".to_string())],
1100 ),
1101 Archivable::Data(vec![
1102 OutputData::SignedInteger(1),
1103 OutputData::UnsignedInteger(4),
1104 ]),
1105 Archivable::Object(
1106 Class {
1107 name: "NSDictionary".to_string(),
1108 version: 0,
1109 },
1110 vec![OutputData::SignedInteger(2)],
1111 ),
1112 Archivable::Object(
1113 Class {
1114 name: "NSString".to_string(),
1115 version: 1,
1116 },
1117 vec![OutputData::String(
1118 "__kIMTextStrikethroughAttributeName".to_string(),
1119 )],
1120 ),
1121 Archivable::Object(
1122 Class {
1123 name: "NSNumber".to_string(),
1124 version: 0,
1125 },
1126 vec![OutputData::SignedInteger(1)],
1127 ),
1128 Archivable::Object(
1129 Class {
1130 name: "NSString".to_string(),
1131 version: 1,
1132 },
1133 vec![OutputData::String(
1134 "__kIMMessagePartAttributeName".to_string(),
1135 )],
1136 ),
1137 Archivable::Object(
1138 Class {
1139 name: "NSNumber".to_string(),
1140 version: 0,
1141 },
1142 vec![OutputData::SignedInteger(0)],
1143 ),
1144 ]),
1145 Some("76A466B8-D21E-4A20-AF62-FF2D3A20D31C".to_string()),
1146 ),
1147 ],
1148 }],
1149 };
1150
1151 assert_eq!(parsed, expected);
1152
1153 let expected_item = Some(expected.parts.first().unwrap());
1154 assert_eq!(parsed.part(0), expected_item);
1155 }
1156}
1157
1158#[cfg(test)]
1159mod test_gen {
1160 use crate::message_types::text_effects::{Style, TextEffect};
1161 use crate::message_types::{edited::EditedMessage, variants::BalloonProvider};
1162 use crate::tables::messages::models::{BubbleComponent, TextAttributes};
1163 use crate::tables::table::AttributedBody;
1164 use plist::Value;
1165 use std::env::current_dir;
1166 use std::fs::File;
1167
1168 #[test]
1169 fn test_parse_edited() {
1170 let plist_path = current_dir()
1171 .unwrap()
1172 .as_path()
1173 .join("test_data/edited_message/Edited.plist");
1174 let plist_data = File::open(plist_path).unwrap();
1175 let plist = Value::from_reader(plist_data).unwrap();
1176 let parsed = EditedMessage::from_map(&plist).unwrap();
1177
1178 let expected_attrs = [
1179 vec![BubbleComponent::Text(vec![TextAttributes::new(
1180 0,
1181 15,
1182 TextEffect::Default,
1183 )])],
1184 vec![BubbleComponent::Text(vec![TextAttributes::new(
1185 0,
1186 6,
1187 TextEffect::Default,
1188 )])],
1189 vec![BubbleComponent::Text(vec![TextAttributes::new(
1190 0,
1191 6,
1192 TextEffect::Default,
1193 )])],
1194 vec![BubbleComponent::Text(vec![TextAttributes::new(
1195 0,
1196 14,
1197 TextEffect::Default,
1198 )])],
1199 ];
1200
1201 for event in parsed.parts {
1202 for (idx, part) in event.edit_history.iter().enumerate() {
1203 assert_eq!(part.body(), expected_attrs[idx]);
1204 }
1205 }
1206 }
1207
1208 #[test]
1209 fn test_parse_edited_to_link() {
1210 let plist_path = current_dir()
1211 .unwrap()
1212 .as_path()
1213 .join("test_data/edited_message/EditedToLink.plist");
1214 let plist_data = File::open(plist_path).unwrap();
1215 let plist = Value::from_reader(plist_data).unwrap();
1216 let parsed = EditedMessage::from_map(&plist).unwrap();
1217
1218 let expected_attrs = [
1219 vec![BubbleComponent::Text(vec![TextAttributes::new(
1220 0,
1221 11,
1222 TextEffect::Default,
1223 )])],
1224 vec![BubbleComponent::Text(vec![TextAttributes::new(
1225 0,
1226 55,
1227 TextEffect::Default,
1228 )])],
1229 ];
1230
1231 for event in parsed.parts {
1232 for (idx, part) in event.edit_history.iter().enumerate() {
1233 assert_eq!(part.body(), expected_attrs[idx]);
1234 }
1235 }
1236 }
1237
1238 #[test]
1239 fn test_parse_edited_to_link_and_back() {
1240 let plist_path = current_dir()
1241 .unwrap()
1242 .as_path()
1243 .join("test_data/edited_message/EditedToLinkAndBack.plist");
1244 let plist_data = File::open(plist_path).unwrap();
1245 let plist = Value::from_reader(plist_data).unwrap();
1246 let parsed = EditedMessage::from_map(&plist).unwrap();
1247
1248 let expected_attrs = [
1249 vec![BubbleComponent::Text(vec![TextAttributes::new(
1250 0,
1251 24,
1252 TextEffect::Default,
1253 )])],
1254 vec![BubbleComponent::Text(vec![TextAttributes::new(
1255 0,
1256 69,
1257 TextEffect::Default,
1258 )])],
1259 vec![BubbleComponent::Text(vec![TextAttributes::new(
1260 0,
1261 39,
1262 TextEffect::Default,
1263 )])],
1264 ];
1265
1266 for event in parsed.parts {
1267 for (idx, part) in event.edit_history.iter().enumerate() {
1268 assert_eq!(part.body(), expected_attrs[idx]);
1269 }
1270 }
1271 }
1272
1273 #[test]
1274 fn test_parse_deleted() {
1275 let plist_path = current_dir()
1276 .unwrap()
1277 .as_path()
1278 .join("test_data/edited_message/Deleted.plist");
1279 let plist_data = File::open(plist_path).unwrap();
1280 let plist = Value::from_reader(plist_data).unwrap();
1281 let parsed = EditedMessage::from_map(&plist).unwrap();
1282
1283 let expected_attrs: [Vec<BubbleComponent<'_>>; 0] = [];
1284
1285 for event in parsed.parts {
1286 for (idx, part) in event.edit_history.iter().enumerate() {
1287 assert_eq!(part.body(), expected_attrs[idx]);
1288 }
1289 }
1290 }
1291
1292 #[test]
1293 fn test_parse_multipart_deleted() {
1294 let plist_path = current_dir()
1295 .unwrap()
1296 .as_path()
1297 .join("test_data/edited_message/MultiPartOneDeleted.plist");
1298 let plist_data = File::open(plist_path).unwrap();
1299 let plist = Value::from_reader(plist_data).unwrap();
1300 let parsed = EditedMessage::from_map(&plist).unwrap();
1301
1302 let expected_attrs: [Vec<BubbleComponent<'_>>; 0] = [];
1303
1304 for event in parsed.parts {
1305 for (idx, part) in event.edit_history.iter().enumerate() {
1306 assert_eq!(part.body(), expected_attrs[idx]);
1307 }
1308 }
1309 }
1310
1311 #[test]
1312 fn test_parse_multipart_edited_and_deleted() {
1313 let plist_path = current_dir()
1314 .unwrap()
1315 .as_path()
1316 .join("test_data/edited_message/EditedAndDeleted.plist");
1317 let plist_data = File::open(plist_path).unwrap();
1318 let plist = Value::from_reader(plist_data).unwrap();
1319 let parsed = EditedMessage::from_map(&plist).unwrap();
1320
1321 let expected_attrs = [
1322 vec![BubbleComponent::Text(vec![TextAttributes::new(
1323 0,
1324 14,
1325 TextEffect::Default,
1326 )])],
1327 vec![BubbleComponent::Text(vec![TextAttributes::new(
1328 0,
1329 26,
1330 TextEffect::Default,
1331 )])],
1332 ];
1333
1334 for event in parsed.parts {
1335 for (idx, part) in event.edit_history.iter().enumerate() {
1336 assert_eq!(part.body(), expected_attrs[idx]);
1337 }
1338 }
1339 }
1340
1341 #[test]
1342 fn test_parse_multipart_edited_and_unsent() {
1343 let plist_path = current_dir()
1344 .unwrap()
1345 .as_path()
1346 .join("test_data/edited_message/EditedAndUnsent.plist");
1347 let plist_data = File::open(plist_path).unwrap();
1348 let plist = Value::from_reader(plist_data).unwrap();
1349 let parsed = EditedMessage::from_map(&plist).unwrap();
1350
1351 for parts in &parsed.parts {
1352 for part in &parts.edit_history {
1353 println!("{:#?}", part.body());
1354 }
1355 }
1356
1357 let expected_attrs: [Vec<BubbleComponent<'_>>; 2] = [
1358 vec![BubbleComponent::Text(vec![TextAttributes::new(
1359 0,
1360 11,
1361 TextEffect::Default,
1362 )])],
1363 vec![BubbleComponent::Text(vec![TextAttributes::new(
1364 0,
1365 23,
1366 TextEffect::Default,
1367 )])],
1368 ];
1369
1370 for event in parsed.parts {
1371 for (idx, part) in event.edit_history.iter().enumerate() {
1372 assert_eq!(part.body(), expected_attrs[idx]);
1373 }
1374 }
1375 }
1376
1377 #[test]
1378 fn test_parse_edited_with_formatting() {
1379 let plist_path = current_dir()
1380 .unwrap()
1381 .as_path()
1382 .join("test_data/edited_message/EditedWithFormatting.plist");
1383 let plist_data = File::open(plist_path).unwrap();
1384 let plist = Value::from_reader(plist_data).unwrap();
1385 let parsed = EditedMessage::from_map(&plist).unwrap();
1386
1387 let expected_attrs: [Vec<BubbleComponent<'_>>; 2] = [
1388 vec![BubbleComponent::Text(vec![TextAttributes::new(
1389 0,
1390 4,
1391 TextEffect::Default,
1392 )])],
1393 vec![BubbleComponent::Text(vec![TextAttributes::new(
1394 0,
1395 4,
1396 TextEffect::Styles(vec![Style::Strikethrough]),
1397 )])],
1398 ];
1399
1400 for event in parsed.parts {
1401 for (idx, part) in event.edit_history.iter().enumerate() {
1402 assert_eq!(part.body(), expected_attrs[idx]);
1403 }
1404 }
1405 }
1406}