imessage_database/message_types/
sticker.rs1use std::fmt::{Display, Formatter};
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 #[must_use]
40 pub fn from_bundle_id(bundle_id: &str) -> Option<Self> {
41 match parse_balloon_bundle_id(Some(bundle_id)) {
42 Some("com.apple.messages.genmoji") => Some(StickerSource::Genmoji),
43 Some(
44 "com.apple.Animoji.StickersApp.MessagesExtension" | "com.apple.Jellyfish.Animoji",
45 ) => Some(StickerSource::Memoji),
46 Some("com.apple.Stickers.UserGenerated.MessagesExtension") => {
47 Some(StickerSource::UserGenerated)
48 }
49 Some(other) => Some(StickerSource::App(other.to_string())),
50 None => None,
51 }
52 }
53}
54
55#[derive(Debug, PartialEq, Eq)]
65pub enum StickerDecoration {
66 GenmojiPrompt(String),
68 Memoji,
70 Effect(StickerEffect),
72 AppName(String),
75}
76
77#[derive(Debug, PartialEq, Eq, Default)]
79pub enum StickerEffect {
80 #[default]
82 Normal,
83 Outline,
85 Comic,
87 Puffy,
89 Shiny,
91 Other(String),
93}
94
95impl StickerEffect {
96 fn from_exif(sticker_effect_type: &str) -> Self {
98 match sticker_effect_type {
99 "stroke" => Self::Outline,
100 "comic" => Self::Comic,
101 "puffy" => Self::Puffy,
102 "iridescent" => Self::Shiny,
103 other => Self::Other(other.to_owned()),
104 }
105 }
106}
107
108impl Display for StickerEffect {
109 fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result {
110 match self {
111 StickerEffect::Normal => write!(fmt, "Normal"),
112 StickerEffect::Outline => write!(fmt, "Outline"),
113 StickerEffect::Comic => write!(fmt, "Comic"),
114 StickerEffect::Puffy => write!(fmt, "Puffy"),
115 StickerEffect::Shiny => write!(fmt, "Shiny"),
116 StickerEffect::Other(name) => write!(fmt, "{name}"),
117 }
118 }
119}
120
121#[must_use]
123pub fn get_sticker_effect(mut heic_data: &[u8]) -> StickerEffect {
124 for idx in 0..heic_data.len() {
126 if idx + STICKER_EFFECT_PREFIX.len() < heic_data.len() {
127 let part = &heic_data[idx..idx + STICKER_EFFECT_PREFIX.len()];
128 if part == STICKER_EFFECT_PREFIX {
129 heic_data = &heic_data[idx + STICKER_EFFECT_PREFIX.len()..];
131 break;
132 }
133 } else {
134 return StickerEffect::Normal;
135 }
136 }
137
138 for idx in 1..heic_data.len() {
140 if idx >= heic_data.len() - 2 {
141 return StickerEffect::Other("Unknown".to_string());
142 }
143 let part = &heic_data[idx..idx + STICKER_EFFECT_SUFFIX.len()];
144
145 if part == STICKER_EFFECT_SUFFIX {
146 heic_data = &heic_data[..idx];
148 break;
149 }
150 }
151 StickerEffect::from_exif(&String::from_utf8_lossy(heic_data))
152}
153
154#[cfg(test)]
155mod tests {
156 use std::env::current_dir;
157 use std::fs::File;
158 use std::io::Read;
159
160 use crate::message_types::sticker::{StickerEffect, get_sticker_effect};
161
162 #[test]
163 fn test_parse_sticker_normal() {
164 let sticker_path = current_dir()
165 .unwrap()
166 .as_path()
167 .join("test_data/stickers/no_effect.heic");
168 let mut file = File::open(sticker_path).unwrap();
169 let mut bytes = vec![];
170 file.read_to_end(&mut bytes).unwrap();
171
172 let effect = get_sticker_effect(&bytes);
173
174 assert_eq!(effect, StickerEffect::Normal);
175 }
176
177 #[test]
178 fn test_parse_sticker_outline() {
179 let sticker_path = current_dir()
180 .unwrap()
181 .as_path()
182 .join("test_data/stickers/outline.heic");
183 let mut file = File::open(sticker_path).unwrap();
184 let mut bytes = vec![];
185 file.read_to_end(&mut bytes).unwrap();
186
187 let effect = get_sticker_effect(&bytes);
188
189 assert_eq!(effect, StickerEffect::Outline);
190 }
191
192 #[test]
193 fn test_parse_sticker_comic() {
194 let sticker_path = current_dir()
195 .unwrap()
196 .as_path()
197 .join("test_data/stickers/comic.heic");
198 let mut file = File::open(sticker_path).unwrap();
199 let mut bytes = vec![];
200 file.read_to_end(&mut bytes).unwrap();
201
202 let effect = get_sticker_effect(&bytes);
203
204 assert_eq!(effect, StickerEffect::Comic);
205 }
206
207 #[test]
208 fn test_parse_sticker_puffy() {
209 let sticker_path = current_dir()
210 .unwrap()
211 .as_path()
212 .join("test_data/stickers/puffy.heic");
213 let mut file = File::open(sticker_path).unwrap();
214 let mut bytes = vec![];
215 file.read_to_end(&mut bytes).unwrap();
216
217 let effect = get_sticker_effect(&bytes);
218
219 assert_eq!(effect, StickerEffect::Puffy);
220 }
221
222 #[test]
223 fn test_parse_sticker_shiny() {
224 let sticker_path = current_dir()
225 .unwrap()
226 .as_path()
227 .join("test_data/stickers/shiny.heic");
228 let mut file = File::open(sticker_path).unwrap();
229 let mut bytes = vec![];
230 file.read_to_end(&mut bytes).unwrap();
231
232 let effect = get_sticker_effect(&bytes);
233
234 assert_eq!(effect, StickerEffect::Shiny);
235 }
236}