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