imessage_database/message_types/
sticker.rs1use std::fmt::Display;
6
7use crate::util::bundle_id::parse_balloon_bundle_id;
8
9const STICKER_EFFECT_PREFIX: [u8; 20] = [
11 115, 116, 105, 99, 107, 101, 114, 69, 102, 102, 101, 99, 116, 58, 116, 121, 112, 101, 61, 34,
12];
13const STICKER_EFFECT_SUFFIX: [u8; 3] = [34, 47, 62];
15
16#[derive(Debug, PartialEq, Eq)]
18pub enum StickerSource {
19 Genmoji,
21 Memoji,
23 UserGenerated,
25 App(String),
27}
28
29impl StickerSource {
30 pub fn from_bundle_id(bundle_id: &str) -> Option<Self> {
40 match parse_balloon_bundle_id(Some(bundle_id)) {
41 Some("com.apple.messages.genmoji") => Some(StickerSource::Genmoji),
42 Some("com.apple.Animoji.StickersApp.MessagesExtension")
43 | Some("com.apple.Jellyfish.Animoji") => Some(StickerSource::Memoji),
44 Some("com.apple.Stickers.UserGenerated.MessagesExtension") => {
45 Some(StickerSource::UserGenerated)
46 }
47 Some(other) => Some(StickerSource::App(other.to_string())),
48 None => None,
49 }
50 }
51}
52
53#[derive(Debug, PartialEq, Eq)]
55pub enum StickerEffect {
56 Normal,
58 Outline,
60 Comic,
61 Puffy,
62 Shiny,
64 Other(String),
65}
66
67impl StickerEffect {
68 fn from_exif(sticker_effect_type: &str) -> Self {
70 match sticker_effect_type {
71 "stroke" => Self::Outline,
72 "comic" => Self::Comic,
73 "puffy" => Self::Puffy,
74 "iridescent" => Self::Shiny,
75 other => Self::Other(other.to_owned()),
76 }
77 }
78}
79
80impl Display for StickerEffect {
81 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 match self {
83 StickerEffect::Normal => write!(fmt, "Normal"),
84 StickerEffect::Outline => write!(fmt, "Outline"),
85 StickerEffect::Comic => write!(fmt, "Comic"),
86 StickerEffect::Puffy => write!(fmt, "Puffy"),
87 StickerEffect::Shiny => write!(fmt, "Shiny"),
88 StickerEffect::Other(name) => write!(fmt, "{name}"),
89 }
90 }
91}
92
93impl Default for StickerEffect {
94 fn default() -> Self {
95 Self::Normal
96 }
97}
98
99pub fn get_sticker_effect(mut heic_data: Vec<u8>) -> StickerEffect {
101 for idx in 0..heic_data.len() {
103 if idx + STICKER_EFFECT_PREFIX.len() < heic_data.len() {
104 let part = &heic_data[idx..idx + STICKER_EFFECT_PREFIX.len()];
105 if part == STICKER_EFFECT_PREFIX {
106 heic_data.drain(..idx + STICKER_EFFECT_PREFIX.len());
108 break;
109 }
110 } else {
111 return StickerEffect::Normal;
112 }
113 }
114
115 for idx in 1..heic_data.len() {
117 if idx >= heic_data.len() - 2 {
118 return StickerEffect::Other("Unknown".to_string());
119 }
120 let part = &heic_data[idx..idx + STICKER_EFFECT_SUFFIX.len()];
121
122 if part == STICKER_EFFECT_SUFFIX {
123 heic_data.truncate(idx);
125 break;
126 }
127 }
128 StickerEffect::from_exif(&String::from_utf8_lossy(&heic_data))
129}
130
131#[cfg(test)]
132mod tests {
133 use std::env::current_dir;
134 use std::fs::File;
135 use std::io::Read;
136
137 use crate::message_types::sticker::{get_sticker_effect, StickerEffect};
138
139 #[test]
140 fn test_parse_sticker_normal() {
141 let sticker_path = current_dir()
142 .unwrap()
143 .as_path()
144 .join("test_data/stickers/no_effect.heic");
145 let mut file = File::open(sticker_path).unwrap();
146 let mut bytes = vec![];
147 file.read_to_end(&mut bytes).unwrap();
148
149 let effect = get_sticker_effect(bytes);
150
151 assert_eq!(effect, StickerEffect::Normal);
152 }
153
154 #[test]
155 fn test_parse_sticker_outline() {
156 let sticker_path = current_dir()
157 .unwrap()
158 .as_path()
159 .join("test_data/stickers/outline.heic");
160 let mut file = File::open(sticker_path).unwrap();
161 let mut bytes = vec![];
162 file.read_to_end(&mut bytes).unwrap();
163
164 let effect = get_sticker_effect(bytes);
165
166 assert_eq!(effect, StickerEffect::Outline);
167 }
168
169 #[test]
170 fn test_parse_sticker_comic() {
171 let sticker_path = current_dir()
172 .unwrap()
173 .as_path()
174 .join("test_data/stickers/comic.heic");
175 let mut file = File::open(sticker_path).unwrap();
176 let mut bytes = vec![];
177 file.read_to_end(&mut bytes).unwrap();
178
179 let effect = get_sticker_effect(bytes);
180
181 assert_eq!(effect, StickerEffect::Comic);
182 }
183
184 #[test]
185 fn test_parse_sticker_puffy() {
186 let sticker_path = current_dir()
187 .unwrap()
188 .as_path()
189 .join("test_data/stickers/puffy.heic");
190 let mut file = File::open(sticker_path).unwrap();
191 let mut bytes = vec![];
192 file.read_to_end(&mut bytes).unwrap();
193
194 let effect = get_sticker_effect(bytes);
195
196 assert_eq!(effect, StickerEffect::Puffy);
197 }
198
199 #[test]
200 fn test_parse_sticker_shiny() {
201 let sticker_path = current_dir()
202 .unwrap()
203 .as_path()
204 .join("test_data/stickers/shiny.heic");
205 let mut file = File::open(sticker_path).unwrap();
206 let mut bytes = vec![];
207 file.read_to_end(&mut bytes).unwrap();
208
209 let effect = get_sticker_effect(bytes);
210
211 assert_eq!(effect, StickerEffect::Shiny);
212 }
213}