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