imessage_database/message_types/
translation.rs1use plist::Value;
5use std::io::Cursor;
6
7use crate::{
8 error::plist::PlistParseError,
9 util::plist::{
10 extract_array_key, extract_bytes_key, extract_dict_idx, extract_dictionary,
11 extract_string_key, parse_ns_keyed_archiver, plist_as_dictionary,
12 },
13};
14
15#[derive(Debug, PartialEq, Eq)]
17pub struct Translation {
18 pub translated_text: String,
20 pub translation_lang: String,
22 pub source_lang: String,
24}
25
26impl Translation {
27 pub fn from_payload(plist: &Value) -> Result<Self, PlistParseError> {
29 let translation_data = extract_bytes_key(plist_as_dictionary(plist)?, "tmp")?;
30
31 let inner_plist = parse_ns_keyed_archiver(
32 &Value::from_reader(Cursor::new(translation_data))
33 .map_err(|_| PlistParseError::NoPayload)?,
34 )?;
35
36 let translation_data = extract_dict_idx(
39 extract_array_key(
40 extract_dictionary(plist_as_dictionary(&inner_plist)?, "0")?,
41 "0",
42 )?,
43 0,
44 )?;
45
46 Ok(Self {
47 translated_text: extract_string_key(
51 extract_dictionary(translation_data, "translatedText")?,
52 "NSString",
53 )?
54 .replace('\u{FFFC}', ""),
55 translation_lang: extract_string_key(translation_data, "translationLanguage")?
56 .to_string(),
57 source_lang: extract_string_key(translation_data, "sourceLanguage")?.to_string(),
58 })
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use std::{env::current_dir, fs::File};
65
66 use plist::Value;
67
68 use crate::message_types::translation::Translation;
69
70 #[test]
71 fn test_parse_translation_with_inline_stickers() {
72 let plist_path = current_dir()
77 .unwrap()
78 .as_path()
79 .join("test_data/app_message/TranslatedWithInlineStickersAndEmojiSummaryInfo.plist");
80 let plist_data = File::open(plist_path).unwrap();
81 let plist = Value::from_reader(plist_data).unwrap();
82
83 let translation = Translation::from_payload(&plist).unwrap();
84
85 assert_eq!(translation.translated_text, "Ceci est un test ");
86 assert_eq!(translation.source_lang, "en_US");
87 assert_eq!(translation.translation_lang, "fr_FR");
88 }
89
90 #[test]
91 fn test_parse_translation() {
92 let plist_path = current_dir()
93 .unwrap()
94 .as_path()
95 .join("test_data/app_message/Translation.plist");
96 let plist_data = File::open(plist_path).unwrap();
97 let plist = Value::from_reader(plist_data).unwrap();
98
99 let translation = Translation::from_payload(&plist).unwrap();
100
101 println!("{:#?}", translation);
102 assert_eq!(
103 translation.translated_text,
104 "Oh, il a traduit ce que j'ai envoyé !"
105 );
106 assert_eq!(translation.source_lang, "en_US");
107 assert_eq!(translation.translation_lang, "fr_FR");
108 }
109
110 #[test]
111 fn test_parse_translation_received() {
112 let plist_path = current_dir()
113 .unwrap()
114 .as_path()
115 .join("test_data/app_message/TranslationReceived.plist");
116 let plist_data = File::open(plist_path).unwrap();
117 let plist = Value::from_reader(plist_data).unwrap();
118
119 let translation = Translation::from_payload(&plist).unwrap();
120
121 println!("{:#?}", translation);
122 assert_eq!(translation.translated_text, "I want chicken");
123 assert_eq!(translation.source_lang, "fr_FR");
124 assert_eq!(translation.translation_lang, "en_US");
125 }
126}