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