;
+ /// Format a sticker, possibly by reading the disk
+ fn format_sticker(&self, attachment: &'a mut Attachment, msg: &'a Message) -> String;
/// Format an app message by parsing some of its fields
fn format_app(
&self,
diff --git a/imessage-exporter/src/exporters/html.rs b/imessage-exporter/src/exporters/html.rs
index 29446f0f..b283cc12 100644
--- a/imessage-exporter/src/exporters/html.rs
+++ b/imessage-exporter/src/exporters/html.rs
@@ -312,20 +312,37 @@ impl<'a> Writer<'a> for HTML<'a> {
}
BubbleType::Attachment => {
match attachments.get_mut(attachment_index) {
- Some(attachment) => match self.format_attachment(attachment, message) {
- Ok(result) => {
- attachment_index += 1;
- self.add_line(&mut formatted_message, &result, "", "");
- }
- Err(result) => {
+ Some(attachment) => {
+ if attachment.is_sticker {
+ let result = self.format_sticker(attachment, message);
self.add_line(
&mut formatted_message,
- result,
- "Unable to locate attachment: ",
- "",
- );
+ &result,
+ "",
+ "
",
+ )
+ } else {
+ match self.format_attachment(attachment, message) {
+ Ok(result) => {
+ attachment_index += 1;
+ self.add_line(
+ &mut formatted_message,
+ &result,
+ "",
+ "
",
+ );
+ }
+ Err(result) => {
+ self.add_line(
+ &mut formatted_message,
+ result,
+ "Unable to locate attachment: ",
+ "",
+ );
+ }
+ }
}
- },
+ }
// Attachment does not exist in attachments table
None => self.add_line(
&mut formatted_message,
@@ -489,6 +506,22 @@ impl<'a> Writer<'a> for HTML<'a> {
});
}
+ fn format_sticker(&self, sticker: &'a mut Attachment, message: &Message) -> String {
+ match self.format_attachment(sticker, message) {
+ Ok(sticker_embed) => {
+ let sticker_effect = sticker.get_sticker_effect(
+ &self.config.options.platform,
+ &self.config.options.db_path,
+ );
+ if let Ok(Some(sticker_effect)) = sticker_effect {
+ return format!("{sticker_embed}\nSent with {sticker_effect} effect
");
+ }
+ sticker_embed
+ }
+ Err(embed) => embed.to_string(),
+ }
+ }
+
fn format_app(
&self,
message: &'a Message,
@@ -581,17 +614,11 @@ impl<'a> Writer<'a> for HTML<'a> {
let who = self.config.who(&msg.handle_id, msg.is_from_me);
// Sticker messages have only one attachment, the sticker image
Ok(match paths.get_mut(0) {
- Some(sticker) => match self.format_attachment(sticker, msg) {
- Ok(img) => {
- Some(format!("{img} from {who}"))
- }
- Err(_) => None,
- },
- None => None,
- }
- .unwrap_or_else(|| {
- format!("Sticker from {who} not found!")
- }))
+ Some(sticker) => self.format_sticker(sticker, msg),
+ None => {
+ format!("Sticker from {who} not found!")
+ }
+ })
}
_ => unreachable!(),
}
@@ -606,7 +633,7 @@ impl<'a> Writer<'a> for HTML<'a> {
ScreenEffect::Balloons => "Sent with Balloons",
ScreenEffect::Heart => "Sent with Heart",
ScreenEffect::Lasers => "Sent with Lasers",
- ScreenEffect::ShootingStar => "Sent with Shooting Start",
+ ScreenEffect::ShootingStar => "Sent with Shooting Star",
ScreenEffect::Sparkles => "Sent with Sparkles",
ScreenEffect::Spotlight => "Sent with Spotlight",
},
@@ -1078,7 +1105,7 @@ impl<'a> HTML<'a> {
#[cfg(test)]
mod tests {
- use std::path::PathBuf;
+ use std::{env::current_dir, path::PathBuf};
use crate::{
app::attachment_manager::AttachmentManager, exporters::exporter::Writer, Config, Exporter,
@@ -1141,6 +1168,7 @@ mod tests {
mime_type: Some("image/png".to_string()),
transfer_name: Some("d.jpg".to_string()),
total_bytes: 100,
+ is_sticker: false,
hide_attachment: 0,
copied_path: None,
}
@@ -1542,6 +1570,31 @@ mod tests {
assert_eq!(actual, Err("d.jpg"));
}
+
+ #[test]
+ fn can_format_html_attachment_sticker() {
+ // Create exporter
+ let options = fake_options();
+ let config = Config::new(options).unwrap();
+ let exporter = HTML::new(&config);
+
+ let mut message = blank();
+ // Set message to sticker variant
+ message.associated_message_type = Some(1000);
+
+ let mut attachment = fake_attachment();
+ attachment.is_sticker = true;
+ let sticker_path = current_dir()
+ .unwrap()
+ .parent()
+ .unwrap()
+ .join("imessage-database/test_data/stickers/outline.heic");
+ attachment.filename = Some(sticker_path.to_string_lossy().to_string());
+
+ let actual = exporter.format_sticker(&mut attachment, &message);
+
+ assert_eq!(actual, "
\nSent with Outline effect
");
+ }
}
#[cfg(test)]
diff --git a/imessage-exporter/src/exporters/resources/style.css b/imessage-exporter/src/exporters/resources/style.css
index 7e8a2042..618c3a7d 100644
--- a/imessage-exporter/src/exporters/resources/style.css
+++ b/imessage-exporter/src/exporters/resources/style.css
@@ -264,6 +264,14 @@ div.reaction {
align-items: center;
}
+div.sticker_effect {
+ opacity: 60%;
+}
+
+div.sticker img {
+ max-width: 5em;
+}
+
.announcement {
text-align: center;
padding: 2vh 1vw 2vh 1vw;
diff --git a/imessage-exporter/src/exporters/txt.rs b/imessage-exporter/src/exporters/txt.rs
index 78d0683a..35a11908 100644
--- a/imessage-exporter/src/exporters/txt.rs
+++ b/imessage-exporter/src/exporters/txt.rs
@@ -201,13 +201,22 @@ impl<'a> Writer<'a> for TXT<'a> {
}
}
BubbleType::Attachment => match attachments.get_mut(attachment_index) {
- Some(attachment) => match self.format_attachment(attachment, message) {
- Ok(result) => {
- attachment_index += 1;
- self.add_line(&mut formatted_message, &result, &indent);
+ Some(attachment) => {
+ if attachment.is_sticker {
+ let result = self.format_sticker(attachment, message);
+ self.add_line(&mut formatted_message, &result, &indent)
+ } else {
+ match self.format_attachment(attachment, message) {
+ Ok(result) => {
+ attachment_index += 1;
+ self.add_line(&mut formatted_message, &result, &indent);
+ }
+ Err(result) => {
+ self.add_line(&mut formatted_message, result, &indent)
+ }
+ }
}
- Err(result) => self.add_line(&mut formatted_message, result, &indent),
- },
+ }
// Attachment does not exist in attachments table
None => self.add_line(&mut formatted_message, "Attachment missing!", &indent),
},
@@ -307,6 +316,23 @@ impl<'a> Writer<'a> for TXT<'a> {
Ok(self.config.message_attachment_path(attachment))
}
+ fn format_sticker(&self, sticker: &'a mut Attachment, message: &Message) -> String {
+ let who = self.config.who(&message.handle_id, message.is_from_me);
+ match self.format_attachment(sticker, message) {
+ Ok(path_to_sticker) => {
+ let sticker_effect = sticker.get_sticker_effect(
+ &self.config.options.platform,
+ &self.config.options.db_path,
+ );
+ if let Ok(Some(sticker_effect)) = sticker_effect {
+ return format!("{sticker_effect} Sticker from {who}: {path_to_sticker}");
+ }
+ format!("Sticker from {who}: {path_to_sticker}")
+ }
+ Err(path) => format!("Sticker from {who}: {path}"),
+ }
+ }
+
fn format_app(
&self,
message: &'a Message,
@@ -379,18 +405,13 @@ impl<'a> Writer<'a> for TXT<'a> {
))
}
Variant::Sticker(_) => {
- let paths = Attachment::from_message(&self.config.db, msg)?;
- Ok(format!(
- "Sticker from {}: {}",
- self.config.who(&msg.handle_id, msg.is_from_me),
- match paths.get(0) {
- Some(sticker) => match sticker.filename.as_ref() {
- Some(path) => path,
- None => sticker.filename(),
- },
- None => "Sticker not found!",
- },
- ))
+ let mut paths = Attachment::from_message(&self.config.db, msg)?;
+ let who = self.config.who(&msg.handle_id, msg.is_from_me);
+ // Sticker messages have only one attachment, the sticker image
+ Ok(match paths.get_mut(0) {
+ Some(sticker) => self.format_sticker(sticker, msg),
+ None => format!("Sticker from {who} not found!"),
+ })
}
_ => unreachable!(),
}
@@ -405,7 +426,7 @@ impl<'a> Writer<'a> for TXT<'a> {
ScreenEffect::Balloons => "Sent with Balloons",
ScreenEffect::Heart => "Sent with Heart",
ScreenEffect::Lasers => "Sent with Lasers",
- ScreenEffect::ShootingStar => "Sent with Shooting Start",
+ ScreenEffect::ShootingStar => "Sent with Shooting Star",
ScreenEffect::Sparkles => "Sent with Sparkles",
ScreenEffect::Spotlight => "Sent with Spotlight",
},
@@ -707,7 +728,7 @@ impl<'a> TXT<'a> {
#[cfg(test)]
mod tests {
- use std::path::PathBuf;
+ use std::{path::PathBuf, env::current_dir};
use crate::{
app::attachment_manager::AttachmentManager, exporters::exporter::Writer, Config, Exporter,
@@ -770,6 +791,7 @@ mod tests {
mime_type: Some("image/png".to_string()),
transfer_name: Some("d.jpg".to_string()),
total_bytes: 100,
+ is_sticker: false,
hide_attachment: 0,
copied_path: None,
}
@@ -1159,6 +1181,31 @@ mod tests {
assert_eq!(actual, Err("d.jpg"));
}
+
+ #[test]
+ fn can_format_html_attachment_sticker() {
+ // Create exporter
+ let options = fake_options();
+ let config = Config::new(options).unwrap();
+ let exporter = TXT::new(&config);
+
+ let mut message = blank();
+ // Set message to sticker variant
+ message.associated_message_type = Some(1000);
+
+ let mut attachment = fake_attachment();
+ attachment.is_sticker = true;
+ let sticker_path = current_dir()
+ .unwrap()
+ .parent()
+ .unwrap()
+ .join("imessage-database/test_data/stickers/outline.heic");
+ attachment.filename = Some(sticker_path.to_string_lossy().to_string());
+
+ let actual = exporter.format_sticker(&mut attachment, &message);
+
+ assert_eq!(actual, "Outline Sticker from Me: /Users/chris/Documents/Code/Rust/imessage-exporter/imessage-database/test_data/stickers/outline.heic");
+ }
}
#[cfg(test)]