imessage_database/tables/messages/
models.rs

1/*!
2 This module contains Data structures and models that represent message data.
3*/
4
5use std::fmt::{Display, Formatter, Result};
6
7use crabstep::{PropertyIterator, deserializer::iter::Property};
8
9use crate::{
10    message_types::text_effects::TextEffect,
11    tables::messages::message::Message,
12    util::typedstream::{as_float, as_nsstring},
13};
14
15/// Defines the parts of a message bubble, i.e. the content that can exist in a single message.
16///
17/// # Component Types
18///
19/// A single iMessage contains data that may be represented across multiple bubbles.
20#[derive(Debug, PartialEq, Clone)]
21pub enum BubbleComponent {
22    /// A text message with associated formatting, generally representing ranges present in a `NSAttributedString`
23    Text(Vec<TextAttributes>),
24    /// An attachment
25    Attachment(AttachmentMeta),
26    /// An [app integration](crate::message_types::app)
27    App,
28    /// A component that was retracted, found by parsing the [`EditedMessage`](crate::message_types::edited::EditedMessage)
29    Retracted,
30}
31
32/// Defines different types of [services](https://support.apple.com/en-us/104972) we can receive messages from.
33#[derive(Debug)]
34pub enum Service<'a> {
35    /// An iMessage
36    #[allow(non_camel_case_types)]
37    iMessage,
38    /// A message sent as SMS
39    SMS,
40    /// A message sent as RCS
41    RCS,
42    /// A message sent via [satellite](https://support.apple.com/en-us/120930)
43    Satellite,
44    /// Any other type of message
45    Other(&'a str),
46    /// Used when service field is not set
47    Unknown,
48}
49
50impl<'a> Service<'a> {
51    /// Creates a [`Service`] enum variant based on the provided service name string.
52    #[must_use]
53    pub fn from(service: Option<&'a str>) -> Self {
54        if let Some(service_name) = service {
55            return match service_name.trim() {
56                "iMessage" => Service::iMessage,
57                "iMessageLite" => Service::Satellite,
58                "SMS" => Service::SMS,
59                "rcs" | "RCS" => Service::RCS,
60                service_name => Service::Other(service_name),
61            };
62        }
63        Service::Unknown
64    }
65}
66
67impl Display for Service<'_> {
68    fn fmt(&self, fmt: &mut Formatter<'_>) -> Result {
69        match self {
70            Service::iMessage => write!(fmt, "iMessage"),
71            Service::SMS => write!(fmt, "SMS"),
72            Service::RCS => write!(fmt, "RCS"),
73            Service::Satellite => write!(fmt, "Satellite"),
74            Service::Other(other) => write!(fmt, "{other}"),
75            Service::Unknown => write!(fmt, "Unknown"),
76        }
77    }
78}
79
80/// Defines ranges of text and associated attributes parsed from [`typedstream`](crate::util::typedstream) `attributedBody` data.
81///
82/// Ranges specify locations where attributes are applied to specific portions of a [`Message`]'s [`text`](crate::tables::messages::Message::text). For example, given message text with a [`Mention`](TextEffect::Mention) like:
83///
84/// ```
85/// let message_text = "What's up, Christopher?";
86/// ```
87///
88/// There will be 3 ranges:
89///
90/// ```
91/// use imessage_database::message_types::text_effects::TextEffect;
92/// use imessage_database::tables::messages::models::{TextAttributes, BubbleComponent};
93///  
94/// let result = vec![BubbleComponent::Text(vec![
95///     TextAttributes::new(0, 11, vec![TextEffect::Default]),  // `What's up, `
96///     TextAttributes::new(11, 22, vec![TextEffect::Mention("+5558675309".to_string())]), // `Christopher`
97///     TextAttributes::new(22, 23, vec![TextEffect::Default])  // `?`
98/// ])];
99/// ```
100#[derive(Debug, PartialEq, Eq, Clone)]
101pub struct TextAttributes {
102    /// The start index of the affected range of message text
103    pub start: usize,
104    /// The end index of the affected range of message text
105    pub end: usize,
106    /// The effects applied to the specified range
107    pub effects: Vec<TextEffect>,
108}
109
110impl TextAttributes {
111    /// Creates a new [`TextAttributes`] with the specified start index, end index, and text effects.
112    #[must_use]
113    pub fn new(start: usize, end: usize, effects: Vec<TextEffect>) -> Self {
114        Self {
115            start,
116            end,
117            effects,
118        }
119    }
120}
121
122/// Representation of attachment metadata used for rendering message body in a conversation feed.
123#[derive(Debug, PartialEq, Default, Clone)]
124pub struct AttachmentMeta {
125    /// GUID of the attachment in the `attachment` table
126    pub guid: Option<String>,
127    /// The transcription, if the attachment was an [audio message](https://support.apple.com/guide/iphone/send-and-receive-audio-messages-iph2e42d3117/ios) sent from or received on a [supported platform](https://www.apple.com/ios/feature-availability/#messages-audio-message-transcription).
128    pub transcription: Option<String>,
129    /// The height of the attachment in points
130    pub height: Option<f64>,
131    /// The width of the attachment in points
132    pub width: Option<f64>,
133    /// The attachment's original filename
134    pub name: Option<String>,
135}
136
137impl AttachmentMeta {
138    /// Populates the attachment metadata fields from a typedstream property iterator.
139    #[must_use]
140    pub(crate) fn from_components<'a>(
141        first_key: &str,
142        components: &'a mut PropertyIterator<'a, 'a>,
143    ) -> Self {
144        let mut meta = Self::default();
145
146        if let Some(mut prop) = components.next() {
147            meta.set_from_key_value(first_key, &mut prop);
148        }
149
150        while let Some(mut key) = components.next() {
151            if let Some(key_name) = as_nsstring(&mut key) {
152                if let Some(mut value) = components.next() {
153                    meta.set_from_key_value(key_name, &mut value);
154                }
155            }
156        }
157
158        meta
159    }
160
161    fn set_from_key_value<'a>(&'a mut self, key: &'a str, value: &'a mut Property<'a, 'a>) {
162        match key {
163            "__kIMFileTransferGUIDAttributeName" => {
164                self.guid = as_nsstring(value).map(String::from);
165            }
166            "IMAudioTranscription" => self.transcription = as_nsstring(value).map(String::from),
167            "__kIMInlineMediaHeightAttributeName" => self.height = as_float(value),
168            "__kIMInlineMediaWidthAttributeName" => self.width = as_float(value),
169            "__kIMFilenameAttributeName" => self.name = as_nsstring(value).map(String::from),
170            _ => {}
171        }
172    }
173}
174
175/// Represents different types of group message actions that can occur in a chat system
176#[derive(Debug)]
177pub enum GroupAction<'a> {
178    /// A new participant has been added to the group
179    ParticipantAdded(i32),
180    /// A participant has been removed from the group
181    ParticipantRemoved(i32),
182    /// The group name has been changed
183    NameChange(&'a str),
184    /// A participant has voluntarily left the group
185    ParticipantLeft,
186    /// The group icon/avatar has been updated with a new image
187    GroupIconChanged,
188    /// The group icon/avatar has been removed, reverting to default
189    GroupIconRemoved,
190}
191
192impl<'a> GroupAction<'a> {
193    /// Creates a new `GroupAction` event type based on the provided message's item and group action data.
194    #[must_use]
195    pub(crate) fn from_message(message: &'a Message) -> Option<Self> {
196        match (
197            message.item_type,
198            message.group_action_type,
199            message.other_handle,
200            &message.group_title,
201        ) {
202            (1, 0, Some(who), _) => Some(Self::ParticipantAdded(who)),
203            (1, 1, Some(who), _) => Some(Self::ParticipantRemoved(who)),
204            (2, _, _, Some(name)) => Some(Self::NameChange(name)),
205            (3, 0, _, _) => Some(Self::ParticipantLeft),
206            (3, 1, _, _) => Some(Self::GroupIconChanged),
207            (3, 2, _, _) => Some(Self::GroupIconRemoved),
208            _ => None,
209        }
210    }
211}