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 #[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)]
57pub enum StickerEffect {
58 Normal,
60 Outline,
62 Comic,
64 Puffy,
66 Shiny,
68 Other(String),
70}
71
72impl StickerEffect {
73 fn from_exif(sticker_effect_type: &str) -> Self {
75 match sticker_effect_type {
76 "stroke" => Self::Outline,
77 "comic" => Self::Comic,
78 "puffy" => Self::Puffy,
79 "iridescent" => Self::Shiny,
80 other => Self::Other(other.to_owned()),
81 }
82 }
83}
84
85impl Display for StickerEffect {
86 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 match self {
88 StickerEffect::Normal => write!(fmt, "Normal"),
89 StickerEffect::Outline => write!(fmt, "Outline"),
90 StickerEffect::Comic => write!(fmt, "Comic"),
91 StickerEffect::Puffy => write!(fmt, "Puffy"),
92 StickerEffect::Shiny => write!(fmt, "Shiny"),
93 StickerEffect::Other(name) => write!(fmt, "{name}"),
94 }
95 }
96}
97
98impl Default for StickerEffect {
99 fn default() -> Self {
100 Self::Normal
101 }
102}
103
104#[must_use]
106pub fn get_sticker_effect(mut heic_data: Vec<u8>) -> StickerEffect {
107 for idx in 0..heic_data.len() {
109 if idx + STICKER_EFFECT_PREFIX.len() < heic_data.len() {
110 let part = &heic_data[idx..idx + STICKER_EFFECT_PREFIX.len()];
111 if part == STICKER_EFFECT_PREFIX {
112 heic_data.drain(..idx + STICKER_EFFECT_PREFIX.len());
114 break;
115 }
116 } else {
117 return StickerEffect::Normal;
118 }
119 }
120
121 for idx in 1..heic_data.len() {
123 if idx >= heic_data.len() - 2 {
124 return StickerEffect::Other("Unknown".to_string());
125 }
126 let part = &heic_data[idx..idx + STICKER_EFFECT_SUFFIX.len()];
127
128 if part == STICKER_EFFECT_SUFFIX {
129 heic_data.truncate(idx);
131 break;
132 }
133 }
134 StickerEffect::from_exif(&String::from_utf8_lossy(&heic_data))
135}
136
137#[cfg(test)]
138mod tests {
139 use std::env::current_dir;
140 use std::fs::File;
141 use std::io::Read;
142
143 use crate::message_types::sticker::{StickerEffect, get_sticker_effect};
144
145 #[test]
146 fn test_parse_sticker_normal() {
147 let sticker_path = current_dir()
148 .unwrap()
149 .as_path()
150 .join("test_data/stickers/no_effect.heic");
151 let mut file = File::open(sticker_path).unwrap();
152 let mut bytes = vec![];
153 file.read_to_end(&mut bytes).unwrap();
154
155 let effect = get_sticker_effect(bytes);
156
157 assert_eq!(effect, StickerEffect::Normal);
158 }
159
160 #[test]
161 fn test_parse_sticker_outline() {
162 let sticker_path = current_dir()
163 .unwrap()
164 .as_path()
165 .join("test_data/stickers/outline.heic");
166 let mut file = File::open(sticker_path).unwrap();
167 let mut bytes = vec![];
168 file.read_to_end(&mut bytes).unwrap();
169
170 let effect = get_sticker_effect(bytes);
171
172 assert_eq!(effect, StickerEffect::Outline);
173 }
174
175 #[test]
176 fn test_parse_sticker_comic() {
177 let sticker_path = current_dir()
178 .unwrap()
179 .as_path()
180 .join("test_data/stickers/comic.heic");
181 let mut file = File::open(sticker_path).unwrap();
182 let mut bytes = vec![];
183 file.read_to_end(&mut bytes).unwrap();
184
185 let effect = get_sticker_effect(bytes);
186
187 assert_eq!(effect, StickerEffect::Comic);
188 }
189
190 #[test]
191 fn test_parse_sticker_puffy() {
192 let sticker_path = current_dir()
193 .unwrap()
194 .as_path()
195 .join("test_data/stickers/puffy.heic");
196 let mut file = File::open(sticker_path).unwrap();
197 let mut bytes = vec![];
198 file.read_to_end(&mut bytes).unwrap();
199
200 let effect = get_sticker_effect(bytes);
201
202 assert_eq!(effect, StickerEffect::Puffy);
203 }
204
205 #[test]
206 fn test_parse_sticker_shiny() {
207 let sticker_path = current_dir()
208 .unwrap()
209 .as_path()
210 .join("test_data/stickers/shiny.heic");
211 let mut file = File::open(sticker_path).unwrap();
212 let mut bytes = vec![];
213 file.read_to_end(&mut bytes).unwrap();
214
215 let effect = get_sticker_effect(bytes);
216
217 assert_eq!(effect, StickerEffect::Shiny);
218 }
219}