1use libsignal_protocol::{ProtocolAddress, ServiceId};
2use std::fmt;
3use uuid::Uuid;
4
5pub use crate::{
6 proto::{
7 attachment_pointer::Flags as AttachmentPointerFlags,
8 data_message::Flags as DataMessageFlags, data_message::Reaction,
9 group_context::Type as GroupType, sync_message, AttachmentPointer,
10 CallMessage, DataMessage, EditMessage, GroupContext, GroupContextV2,
11 NullMessage, PniSignatureMessage, ReceiptMessage, StoryMessage,
12 SyncMessage, TypingMessage,
13 },
14 push_service::ServiceError,
15 ServiceIdExt,
16};
17
18mod data_message;
19mod story_message;
20
21#[derive(Clone, Debug)]
22pub struct Metadata {
23 pub sender: ServiceId,
24 pub destination: ServiceId,
25 pub sender_device: u32,
26 pub timestamp: u64,
27 pub needs_receipt: bool,
28 pub unidentified_sender: bool,
29 pub was_plaintext: bool,
30
31 pub server_guid: Option<Uuid>,
35}
36
37impl fmt::Display for Metadata {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 write!(
40 f,
41 "Metadata {{ sender: {}, guid: {} }}",
42 self.sender.service_id_string(),
43 self.server_guid
45 .map(|u| u.to_string())
46 .as_deref()
47 .unwrap_or("None"),
48 )
49 }
50}
51
52impl Metadata {
53 pub(crate) fn protocol_address(&self) -> ProtocolAddress {
54 self.sender.to_protocol_address(self.sender_device)
55 }
56}
57
58#[derive(Clone, Debug)]
59pub struct Content {
60 pub metadata: Metadata,
61 pub body: ContentBody,
62}
63
64impl Content {
65 pub fn from_body(body: impl Into<ContentBody>, metadata: Metadata) -> Self {
66 Self {
67 metadata,
68 body: body.into(),
69 }
70 }
71
72 #[expect(clippy::result_large_err)]
74 pub fn from_proto(
75 p: crate::proto::Content,
76 metadata: Metadata,
77 ) -> Result<Self, ServiceError> {
78 #[allow(clippy::manual_map)]
84 if let Some(msg) = p.data_message {
85 Ok(Self::from_body(msg, metadata))
86 } else if let Some(msg) = p.sync_message {
87 Ok(Self::from_body(msg, metadata))
88 } else if let Some(msg) = p.call_message {
89 Ok(Self::from_body(msg, metadata))
90 } else if let Some(msg) = p.receipt_message {
91 Ok(Self::from_body(msg, metadata))
92 } else if let Some(msg) = p.typing_message {
93 Ok(Self::from_body(msg, metadata))
94 } else if let Some(msg) = p.story_message {
99 Ok(Self::from_body(msg, metadata))
100 } else if let Some(msg) = p.pni_signature_message {
101 Ok(Self::from_body(msg, metadata))
102 } else if let Some(msg) = p.edit_message {
103 Ok(Self::from_body(msg, metadata))
104 } else {
105 Err(ServiceError::UnsupportedContent)
106 }
107 }
108}
109
110impl fmt::Display for ContentBody {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 match self {
113 Self::NullMessage(_) => write!(f, "NullMessage"),
114 Self::DataMessage(m) => {
115 match (&m.body, &m.reaction, m.attachments.len()) {
116 (Some(body), _, 0) => {
117 write!(f, "DataMessage({})", body)
118 },
119 (Some(body), _, n) => {
120 write!(f, "DataMessage({}, attachments: {n})", body)
121 },
122 (None, Some(emoji), _) => {
123 write!(
124 f,
125 "DataMessage(reaction: {})",
126 emoji.emoji.as_deref().unwrap_or("None")
127 )
128 },
129 (None, _, n) if n > 0 => {
130 write!(f, "DataMessage(attachments: {n})")
131 },
132 _ => {
133 write!(f, "{self:?}")
134 },
135 }
136 },
137 Self::SynchronizeMessage(_) => write!(f, "SynchronizeMessage"),
138 Self::CallMessage(_) => write!(f, "CallMessage"),
139 Self::ReceiptMessage(_) => write!(f, "ReceiptMessage"),
140 Self::TypingMessage(_) => write!(f, "TypingMessage"),
141 Self::StoryMessage(_) => write!(f, "StoryMessage"),
144 Self::PniSignatureMessage(_) => write!(f, "PniSignatureMessage"),
145 Self::EditMessage(_) => write!(f, "EditMessage"),
146 }
147 }
148}
149
150#[derive(Clone, Debug)]
151#[allow(clippy::large_enum_variant)]
152pub enum ContentBody {
153 NullMessage(NullMessage),
154 DataMessage(DataMessage),
155 SynchronizeMessage(SyncMessage),
156 CallMessage(CallMessage),
157 ReceiptMessage(ReceiptMessage),
158 TypingMessage(TypingMessage),
159 StoryMessage(StoryMessage),
162 PniSignatureMessage(PniSignatureMessage),
163 EditMessage(EditMessage),
164}
165
166impl ContentBody {
167 pub fn into_proto(self) -> crate::proto::Content {
168 match self {
169 Self::NullMessage(msg) => crate::proto::Content {
170 null_message: Some(msg),
171 ..Default::default()
172 },
173 Self::DataMessage(msg) => crate::proto::Content {
174 data_message: Some(msg),
175 ..Default::default()
176 },
177 Self::SynchronizeMessage(msg) => crate::proto::Content {
178 sync_message: Some(msg),
179 ..Default::default()
180 },
181 Self::CallMessage(msg) => crate::proto::Content {
182 call_message: Some(msg),
183 ..Default::default()
184 },
185 Self::ReceiptMessage(msg) => crate::proto::Content {
186 receipt_message: Some(msg),
187 ..Default::default()
188 },
189 Self::TypingMessage(msg) => crate::proto::Content {
190 typing_message: Some(msg),
191 ..Default::default()
192 },
193 Self::StoryMessage(msg) => crate::proto::Content {
204 story_message: Some(msg),
205 ..Default::default()
206 },
207 Self::PniSignatureMessage(msg) => crate::proto::Content {
208 pni_signature_message: Some(msg),
209 ..Default::default()
210 },
211 Self::EditMessage(msg) => crate::proto::Content {
212 edit_message: Some(msg),
213 ..Default::default()
214 },
215 }
216 }
217}
218
219macro_rules! impl_from_for_content_body {
220 ($enum:ident ($t:ty)) => {
221 impl From<$t> for ContentBody {
222 fn from(inner: $t) -> ContentBody {
223 ContentBody::$enum(inner)
224 }
225 }
226 };
227}
228
229impl_from_for_content_body!(NullMessage(NullMessage));
230impl_from_for_content_body!(DataMessage(DataMessage));
231impl_from_for_content_body!(SynchronizeMessage(SyncMessage));
232impl_from_for_content_body!(CallMessage(CallMessage));
233impl_from_for_content_body!(ReceiptMessage(ReceiptMessage));
234impl_from_for_content_body!(TypingMessage(TypingMessage));
235impl_from_for_content_body!(StoryMessage(StoryMessage));
240impl_from_for_content_body!(PniSignatureMessage(PniSignatureMessage));
241impl_from_for_content_body!(EditMessage(EditMessage));