Skip to content

Commit

Permalink
Merge pull request #231 from ReagentX/feat/cs/edited-message-struct
Browse files Browse the repository at this point in the history
Feat/cs/edited message struct
  • Loading branch information
ReagentX committed Feb 10, 2024
2 parents 6ceaec8 + 73604c8 commit 2dc3d03
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 121 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Documentation for the library is located [here](imessage-database/README.md).

### Supported Features

This crate supports every iMessage feature as of macOS 14.1.1 (23B81) and iOS 17.2 (21C62):
This crate supports every iMessage feature as of macOS 14.3.1 (23D60) and iOS 17.3.1 (21D61):

- Multi-part messages
- Replies/Threads
Expand Down
130 changes: 58 additions & 72 deletions imessage-database/src/message_types/edited.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ use crate::{
},
};

#[derive(Debug, PartialEq, Eq)]
pub struct EditedEvent<'a> {
/// The date the messages were edited
pub date: i64,
/// The content of the edited messages in [`streamtyped`](crate::util::streamtyped) format
pub text: String,
/// A GUID reference to another message
pub guid: Option<&'a str>,
}

impl<'a> EditedEvent<'a> {
fn new(date: i64, text: String, guid: Option<&'a str>) -> Self {
Self { date, text, guid }
}
}

/// iMessage permits editing sent messages up to five times
/// within 15 minutes of sending the first message and unsending
/// sent messages within 2 minutes.
Expand All @@ -34,12 +50,7 @@ use crate::{
/// Apple describes editing and unsending messages [here](https://support.apple.com/guide/iphone/unsend-and-edit-messages-iphe67195653/ios).
#[derive(Debug, PartialEq, Eq)]
pub struct EditedMessage<'a> {
/// The dates the messages were edited
pub dates: Vec<i64>,
/// The content of the edited messages in [`streamtyped`](crate::util::streamtyped) format
pub texts: Vec<String>,
/// A GUID reference to another message
pub guids: Vec<Option<&'a str>>,
pub events: Vec<EditedEvent<'a>>,
}

impl<'a> BalloonProvider<'a> for EditedMessage<'a> {
Expand Down Expand Up @@ -75,9 +86,7 @@ impl<'a> BalloonProvider<'a> for EditedMessage<'a> {

let guid = message_data.get("bcg").and_then(|item| item.as_string());

edited.dates.push(timestamp);
edited.texts.push(text);
edited.guids.push(guid);
edited.events.push(EditedEvent::new(timestamp, text, guid));
}

Ok(edited)
Expand All @@ -87,44 +96,35 @@ impl<'a> BalloonProvider<'a> for EditedMessage<'a> {
impl<'a> EditedMessage<'a> {
/// A new empty edited message
fn empty() -> Self {
EditedMessage {
dates: Vec::new(),
texts: Vec::new(),
guids: Vec::new(),
}
EditedMessage { events: Vec::new() }
}

/// A new message with a preallocated capacity
fn with_capacity(capacity: usize) -> Self {
EditedMessage {
dates: Vec::with_capacity(capacity),
texts: Vec::with_capacity(capacity),
guids: Vec::with_capacity(capacity),
events: Vec::with_capacity(capacity),
}
}

/// `true` if the message was deleted, `false` if it was edited
pub fn is_deleted(&self) -> bool {
self.texts.is_empty()
self.events.is_empty()
}

/// Gets a tuple for the message at the provided position
pub fn item_at(&self, position: usize) -> Option<(&i64, &str, &Option<&str>)> {
Some((
self.dates.get(position)?,
self.texts.get(position)?,
self.guids.get(position)?,
))
pub fn item_at(&self, position: usize) -> Option<&EditedEvent> {
self.events.get(position)
}

/// Gets the number of items in the edit history
pub fn items(&self) -> usize {
self.texts.len()
self.events.len()
}
}

#[cfg(test)]
mod tests {
use crate::message_types::edited::EditedEvent;
use crate::message_types::{edited::EditedMessage, variants::BalloonProvider};
use plist::Value;
use std::env::current_dir;
Expand All @@ -141,29 +141,18 @@ mod tests {
let parsed = EditedMessage::from_map(&plist).unwrap();

let expected = EditedMessage {
dates: vec![
690513474000000000,
690513480000000000,
690513485000000000,
690513494000000000,
events: vec![
EditedEvent::new(690513474000000000, "First message ".to_string(), None),
EditedEvent::new(690513480000000000, "Edit 1".to_string(), None),
EditedEvent::new(690513485000000000, "Edit 2".to_string(), None),
EditedEvent::new(690513494000000000, "Edited message".to_string(), None),
],
texts: vec![
"First message ".to_string(),
"Edit 1".to_string(),
"Edit 2".to_string(),
"Edited message".to_string(),
],
guids: vec![None, None, None, None],
};

assert_eq!(parsed, expected);
assert_eq!(parsed.items(), 4);

let expected_item = Some((
expected.dates.first().unwrap(),
expected.texts.first().unwrap().as_str(),
expected.guids.first().unwrap(),
));
let expected_item = Some(expected.events.first().unwrap());
assert_eq!(parsed.item_at(0), expected_item);
}

Expand All @@ -178,22 +167,20 @@ mod tests {
let parsed = EditedMessage::from_map(&plist).unwrap();

let expected = EditedMessage {
dates: vec![690514004000000000, 690514772000000000],
texts: vec![
"here we go!".to_string(),
"https://github.com/ReagentX/imessage-exporter/issues/10".to_string(),
events: vec![
EditedEvent::new(690514004000000000, "here we go!".to_string(), None),
EditedEvent::new(
690514772000000000,
"https://github.com/ReagentX/imessage-exporter/issues/10".to_string(),
Some("292BF9C6-C9B8-4827-BE65-6EA1C9B5B384"),
),
],
guids: vec![None, Some("292BF9C6-C9B8-4827-BE65-6EA1C9B5B384")],
};

assert_eq!(parsed, expected);
assert_eq!(parsed.items(), 2);

let expected_item = Some((
expected.dates.first().unwrap(),
expected.texts.first().unwrap().as_str(),
expected.guids.first().unwrap(),
));
let expected_item = Some(expected.events.first().unwrap());
assert_eq!(parsed.item_at(0), expected_item);
}

Expand All @@ -208,27 +195,30 @@ mod tests {
let parsed = EditedMessage::from_map(&plist).unwrap();

let expected = EditedMessage {
dates: vec![690514809000000000, 690514819000000000, 690514834000000000],
texts: vec![
"This is a normal message".to_string(),
"Edit to a url https://github.com/ReagentX/imessage-exporter/issues/10".to_string(),
"And edit it back to a normal message...".to_string(),
],
guids: vec![
None,
Some("0B9103FE-280C-4BD0-A66F-4EDEE3443247"),
Some("0D93DF88-05BA-4418-9B20-79918ADD9923"),
events: vec![
EditedEvent::new(
690514809000000000,
"This is a normal message".to_string(),
None,
),
EditedEvent::new(
690514819000000000,
"Edit to a url https://github.com/ReagentX/imessage-exporter/issues/10"
.to_string(),
Some("0B9103FE-280C-4BD0-A66F-4EDEE3443247"),
),
EditedEvent::new(
690514834000000000,
"And edit it back to a normal message...".to_string(),
Some("0D93DF88-05BA-4418-9B20-79918ADD9923"),
),
],
};

assert_eq!(parsed, expected);
assert_eq!(parsed.items(), 3);

let expected_item = Some((
expected.dates.first().unwrap(),
expected.texts.first().unwrap().as_str(),
expected.guids.first().unwrap(),
));
let expected_item = Some(expected.events.first().unwrap());
assert_eq!(parsed.item_at(0), expected_item);
}

Expand All @@ -242,11 +232,7 @@ mod tests {
let plist = Value::from_reader(plist_data).unwrap();
let parsed = EditedMessage::from_map(&plist).unwrap();

let expected = EditedMessage {
dates: vec![],
texts: vec![],
guids: vec![],
};
let expected = EditedMessage { events: vec![] };

assert_eq!(parsed, expected);
assert!(parsed.is_deleted());
Expand Down
39 changes: 18 additions & 21 deletions imessage-exporter/src/exporters/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -721,29 +721,26 @@ impl<'a> Writer<'a> for HTML<'a> {
} else {
out_s.push_str("<table>");

for i in 0..edited_message.items() {
let last = i == edited_message.items() - 1;

if let Some((timestamp, text, _)) = edited_message.item_at(i) {
let clean_text = sanitize_html(text);
match previous_timestamp {
None => out_s.push_str(&self.edited_to_html("", &clean_text, last)),
Some(prev_timestamp) => {
let end = get_local_time(timestamp, &self.config.offset);
let start = get_local_time(prev_timestamp, &self.config.offset);

let diff = readable_diff(start, end).unwrap_or_default();
out_s.push_str(&self.edited_to_html(
&format!("Edited {diff} later"),
&clean_text,
last,
));
}
for (idx, event) in edited_message.events.iter().enumerate() {
let last = idx == edited_message.items() - 1;
let clean_text = sanitize_html(&event.text);
match previous_timestamp {
None => out_s.push_str(&self.edited_to_html("", &clean_text, last)),
Some(prev_timestamp) => {
let end = get_local_time(&event.date, &self.config.offset);
let start = get_local_time(prev_timestamp, &self.config.offset);

let diff = readable_diff(start, end).unwrap_or_default();
out_s.push_str(&self.edited_to_html(
&format!("Edited {diff} later"),
&clean_text,
last,
));
}

// Update the previous timestamp for the next loop
previous_timestamp = Some(timestamp);
}

// Update the previous timestamp for the next loop
previous_timestamp = Some(&event.date);
}

out_s.push_str("</table>");
Expand Down
51 changes: 24 additions & 27 deletions imessage-exporter/src/exporters/txt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -502,36 +502,33 @@ impl<'a> Writer<'a> for TXT<'a> {
out_s.push_str(who);
out_s.push_str(" deleted a message.");
} else {
for i in 0..edited_message.items() {
// If a message exists, build a string for it
if let Some((timestamp, text, _)) = edited_message.item_at(i) {
match previous_timestamp {
// Original message get an absolute timestamp
None => {
let parsed_timestamp =
format(&get_local_time(timestamp, &self.config.offset));
out_s.push_str(&parsed_timestamp);
out_s.push(' ');
}
// Subsequent edits get a relative timestamp
Some(prev_timestamp) => {
let end = get_local_time(timestamp, &self.config.offset);
let start = get_local_time(prev_timestamp, &self.config.offset);
if let Some(diff) = readable_diff(start, end) {
out_s.push_str(indent);
out_s.push_str("Edited ");
out_s.push_str(&diff);
out_s.push_str(" later: ");
}
for event in &edited_message.events {
match previous_timestamp {
// Original message get an absolute timestamp
None => {
let parsed_timestamp =
format(&get_local_time(&event.date, &self.config.offset));
out_s.push_str(&parsed_timestamp);
out_s.push(' ');
}
// Subsequent edits get a relative timestamp
Some(prev_timestamp) => {
let end = get_local_time(&event.date, &self.config.offset);
let start = get_local_time(prev_timestamp, &self.config.offset);
if let Some(diff) = readable_diff(start, end) {
out_s.push_str(indent);
out_s.push_str("Edited ");
out_s.push_str(&diff);
out_s.push_str(" later: ");
}
};
}
};

// Update the previous timestamp for the next loop
previous_timestamp = Some(timestamp);
// Update the previous timestamp for the next loop
previous_timestamp = Some(&event.date);

// Render the message text
self.add_line(&mut out_s, text, indent);
}
// Render the message text
self.add_line(&mut out_s, &event.text, indent);
}
}

Expand Down

0 comments on commit 2dc3d03

Please sign in to comment.