imessage_database/message_types/
music.rs1use plist::Value;
6
7use crate::{
8 error::plist::PlistParseError,
9 message_types::variants::BalloonProvider,
10 util::plist::{get_string_from_dict, get_string_from_nested_dict, get_value_from_dict},
11};
12
13#[derive(Debug, PartialEq, Eq)]
16pub struct MusicMessage<'a> {
17 pub url: Option<&'a str>,
19 pub preview: Option<&'a str>,
21 pub artist: Option<&'a str>,
23 pub album: Option<&'a str>,
25 pub track_name: Option<&'a str>,
27 pub lyrics: Option<Vec<&'a str>>,
29}
30
31impl<'a> BalloonProvider<'a> for MusicMessage<'a> {
32 fn from_map(payload: &'a Value) -> Result<Self, PlistParseError> {
33 if let Ok((music_metadata, body)) = MusicMessage::get_body_and_url(payload) {
34 if get_string_from_dict(music_metadata, "album").is_none() {
36 return Err(PlistParseError::WrongMessageType);
37 }
38
39 return Ok(Self {
40 url: get_string_from_nested_dict(body, "URL"),
41 preview: get_string_from_nested_dict(music_metadata, "previewURL"),
42 artist: get_string_from_dict(music_metadata, "artist"),
43 album: get_string_from_dict(music_metadata, "album"),
44 track_name: get_string_from_dict(music_metadata, "name"),
45 lyrics: get_value_from_dict(music_metadata, "lyricExcerpt")
46 .and_then(|l| get_string_from_dict(l, "lyrics"))
47 .map(|lyrics| lyrics.split("\n").collect()),
48 });
49 }
50 Err(PlistParseError::NoPayload)
51 }
52}
53
54impl<'a> MusicMessage<'a> {
55 fn get_body_and_url(payload: &'a Value) -> Result<(&'a Value, &'a Value), PlistParseError> {
60 let base = payload
61 .as_dictionary()
62 .ok_or_else(|| {
63 PlistParseError::InvalidType("root".to_string(), "dictionary".to_string())
64 })?
65 .get("richLinkMetadata")
66 .ok_or_else(|| PlistParseError::MissingKey("richLinkMetadata".to_string()))?;
67 Ok((
68 base.as_dictionary()
69 .ok_or_else(|| {
70 PlistParseError::InvalidType("root".to_string(), "dictionary".to_string())
71 })?
72 .get("specialization")
73 .ok_or_else(|| PlistParseError::MissingKey("specialization".to_string()))?,
74 base,
75 ))
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use crate::{
82 message_types::{music::MusicMessage, variants::BalloonProvider},
83 util::plist::parse_ns_keyed_archiver,
84 };
85 use plist::Value;
86 use std::env::current_dir;
87 use std::fs::File;
88
89 #[test]
90 fn test_parse_apple_music() {
91 let plist_path = current_dir()
92 .unwrap()
93 .as_path()
94 .join("test_data/music_message/AppleMusic.plist");
95 let plist_data = File::open(plist_path).unwrap();
96 let plist = Value::from_reader(plist_data).unwrap();
97 let parsed = parse_ns_keyed_archiver(&plist).unwrap();
98
99 let balloon = MusicMessage::from_map(&parsed).unwrap();
100 let expected = MusicMessage {
101 url: Some(
102 "https://music.apple.com/us/album/%D0%BF%D0%B5%D1%81%D0%BD%D1%8C-1/1539641998?i=1539641999",
103 ),
104 preview: Some("https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview115/v4/b2/65/b3/b265b31f-facb-3ea3-e6bc-91a8d01c9b2f/mzaf_18233159060539450284.plus.aac.ep.m4a"),
105 artist: Some("БАТЮШКА"),
106 album: Some("Панихида"),
107 track_name: Some("Песнь 1"),
108 lyrics: None,
109 };
110
111 assert_eq!(balloon, expected);
112 }
113
114 #[test]
115 fn test_parse_apple_music_lyrics() {
116 let plist_path = current_dir()
117 .unwrap()
118 .as_path()
119 .join("test_data/music_message/AppleMusicLyrics.plist");
120 let plist_data = File::open(plist_path).unwrap();
121 let plist = Value::from_reader(plist_data).unwrap();
122 let parsed = parse_ns_keyed_archiver(&plist).unwrap();
123
124 println!("{:#?}", parsed);
125
126 let balloon = MusicMessage::from_map(&parsed).unwrap();
127 let expected = MusicMessage {
128 url: Some(
129 "https://music.apple.com/us/lyrics/1329891623?ts=11.108&te=16.031&l=en&tk=2.v1.VsuX9f%2BaT1PyrgMgIT7ANQ%3D%3D&itsct=sharing_msg_lyrics&itscg=50401",
130 ),
131 preview: None,
132 artist: Some("Dual Core"),
133 album: Some("Downtime"),
134 track_name: Some("Another Chapter"),
135 lyrics: Some(vec!["I remember when it all started, something from a dream", "Addicted to the black and green letters on my screen"]),
136 };
137
138 assert_eq!(balloon, expected);
139 }
140}