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