imessage_database/message_types/
variants.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/*!
 Variants represent the different types of iMessages that exist in the `messages` table.
*/

use std::fmt::Display;

use plist::Value;

use crate::{
    error::plist::PlistParseError,
    message_types::{
        app_store::AppStoreMessage, collaboration::CollaborationMessage, music::MusicMessage,
        placemark::PlacemarkMessage, url::URLMessage,
    },
};

/// # Tapbacks
///
/// [Tapbacks](https://support.apple.com/guide/messages/react-with-tapbacks-icht504f698a/mac) look like normal messages in the database. Only the latest tapback
/// is stored. For example:
///
/// - user receives message -> user likes message
///   - This will create a message and a like message
/// - user receives message -> user likes message -> user unlikes message
///   - This will create a message and a like message
///   - but that like message will get dropped when the unlike message arrives
///   - When messages drop the ROWIDs become non-sequential: the ID of the dropped message row is not reused
///   - This means unliking an old message will make it look like the tapback was applied/removed at the
///     time of latest change; the history of tapback statuses is not kept
///
/// ## Technical detail
///
/// The index specified by the prefix maps to the index of the body part given by [`Message::body()`](crate::tables::messages::Message::body).
///
/// - `bp:` GUID prefix for bubble message tapbacks (url previews, apps, etc).
/// - `p:0/` GUID prefix for normal messages (body text, attachments).
///
/// If a message has 3 attachments followed by some text:
/// - 0 is the first image
/// - 1 is the second image
/// - 2 is the third image
/// - 3 is the text of the message
///
/// In this example, a Like on `p:2/` is a like on the third image.
#[derive(Debug)]
pub enum Tapback<'a> {
    /// Heart
    Loved,
    /// Thumbs up
    Liked,
    /// Thumbs down
    Disliked,
    /// Laughing face
    Laughed,
    /// Exclamation points
    Emphasized,
    /// Question marks
    Questioned,
    /// Custom emoji tapbacks
    Emoji(Option<&'a str>),
}

impl<'a> Display for Tapback<'a> {
    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Tapback::Emoji(emoji) => match emoji {
                Some(e) => write!(fmt, "{e}"),
                None => write!(fmt, "Unknown emoji!"),
            },
            _ => write!(fmt, "{self:?}"),
        }
    }
}

/// Application Messages
///
/// Messages sent via an app's iMessage integration will send in a special balloon instead of a normal
/// text balloon. This represents the different variants of message balloon.
#[derive(Debug)]
pub enum CustomBalloon<'a> {
    /// Generic third party [applications](crate::message_types::app)
    Application(&'a str),
    /// [URL](crate::message_types::url) previews
    URL,
    /// Handwritten animated messages
    Handwriting,
    /// Digital Touch message
    DigitalTouch,
    /// Apple Pay (one of Sent, Requested, Received)
    ApplePay,
    /// Fitness.app messages
    Fitness,
    /// Photos.app slideshow messages
    Slideshow,
    /// [Check In](https://support.apple.com/guide/iphone/use-check-in-iphc143bb7e9/ios) messages
    CheckIn,
    /// Find My messages
    FindMy,
}

/// URL Message Types
///
/// Apple sometimes overloads `com.apple.messages.URLBalloonProvider` with
/// other types of messages; this enum represents those variants.
#[derive(Debug)]
pub enum URLOverride<'a> {
    /// [`URL`](crate::message_types::url) previews
    Normal(URLMessage<'a>),
    /// [`Apple Music`](crate::message_types::music) messages
    AppleMusic(MusicMessage<'a>),
    /// [`App Store`](crate::message_types::app_store) messages
    AppStore(AppStoreMessage<'a>),
    /// [`Collaboration`](crate::message_types::collaboration) messages
    Collaboration(CollaborationMessage<'a>),
    /// [`Placemark`](crate::message_types::placemark) messages
    SharedPlacemark(PlacemarkMessage<'a>),
}

/// Announcement Message Types
///
/// Announcements are messages sent to a thread for actions that are not balloons, i.e.
/// updating the name of the group or changing the group photo
#[derive(Debug)]
pub enum Announcement<'a> {
    /// Someone changed the name of the group
    NameChange(&'a str),
    /// Someone updated the group photo
    PhotoChange,
    /// All parts of the message were unsent
    FullyUnsent,
    /// Types that may occur in the future, i.e. someone leaving or joining a group
    Unknown(&'a i32),
}

/// Message variant container
///
/// Messages can exist as one of many different variants, this encapsulates
/// all of the possibilities.
#[derive(Debug)]
pub enum Variant<'a> {
    /// A [tapback](https://support.apple.com/guide/messages/react-with-tapbacks-icht504f698a/mac)
    ///
    /// The `usize` indicates the index of the message's [`body()`](crate::tables::messages::Message::body) the tapback is applied to.
    ///
    /// The boolean indicates whether the tapback was applied (`true`) or removed (`false`).
    Tapback(usize, bool, Tapback<'a>),
    /// A sticker message, either placed on another message or by itself
    ///
    /// If the sticker is a tapback, the `usize` indicates the index of the message's [`body()`](crate::tables::messages::Message::body) the tapback is applied to.
    ///
    /// If the sticker is a normal message, it is treated like an attachment, and the message's [`body()`](crate::tables::messages::Message::body) indicates the location.
    Sticker(usize),
    /// Container for new or unknown messages
    Unknown(i32),
    /// An [iMessage app](https://support.apple.com/en-us/HT206906) generated message
    App(CustomBalloon<'a>),
    /// An iMessage with a standard text body that may include attachments
    Normal,
    /// A message that has been edited or unsent
    Edited,
    /// A SharePlay message
    SharePlay,
}

/// Defines behavior for different types of messages that have custom balloons
pub trait BalloonProvider<'a> {
    /// Creates the object from a `HashMap` of item attributes
    fn from_map(payload: &'a Value) -> Result<Self, PlistParseError>
    where
        Self: Sized;
}