1use crate::groups::GroupError;
2use crate::messages::enrichment::EnrichMessageError;
3use prost::Message;
4use xmtp_content_types::actions::{Actions, ActionsCodec};
5use xmtp_content_types::group_updated::GroupUpdatedCodec;
6use xmtp_content_types::intent::{Intent, IntentCodec};
7use xmtp_content_types::leave_request::LeaveRequestCodec;
8use xmtp_content_types::multi_remote_attachment::MultiRemoteAttachmentCodec;
9use xmtp_content_types::reaction::{LegacyReactionCodec, ReactionCodec};
10use xmtp_content_types::read_receipt::ReadReceiptCodec;
11use xmtp_content_types::remote_attachment::RemoteAttachmentCodec;
12use xmtp_content_types::reply::ReplyCodec;
13use xmtp_content_types::transaction_reference::TransactionReferenceCodec;
14use xmtp_content_types::wallet_send_calls::{WalletSendCalls, WalletSendCallsCodec};
15use xmtp_content_types::{CodecError, ContentCodec};
16use xmtp_content_types::{
17 attachment::{Attachment, AttachmentCodec},
18 markdown::MarkdownCodec,
19 read_receipt::ReadReceipt,
20 remote_attachment::RemoteAttachment,
21 text::TextCodec,
22 transaction_reference::TransactionReference,
23};
24use xmtp_db::group_message::StoredGroupMessage;
25use xmtp_db::group_message::{DeliveryStatus, GroupMessageKind};
26use xmtp_proto::types::GroupId;
27use xmtp_proto::xmtp::mls::message_contents::{
28 ContentTypeId, EncodedContent, GroupUpdated,
29 content_types::{LeaveRequest, MultiRemoteAttachment, ReactionV2},
30};
31
32#[derive(Debug, Clone)]
33pub struct Reply {
34 pub in_reply_to: Option<Box<DecodedMessage>>,
37 pub content: Box<MessageBody>,
38 pub reference_id: String,
39}
40
41#[derive(Debug, Clone)]
43pub struct Text {
44 pub content: String,
45}
46
47#[derive(Debug, Clone)]
49pub struct Markdown {
50 pub content: String,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub enum DeletedBy {
55 Sender,
57 Admin(String), }
60
61#[derive(Debug, Clone)]
62pub enum MessageBody {
63 Text(Text),
64 Markdown(Markdown),
65 Reply(Reply),
66 Reaction(ReactionV2),
67 Attachment(Attachment),
68 RemoteAttachment(RemoteAttachment),
69 MultiRemoteAttachment(MultiRemoteAttachment),
70 TransactionReference(TransactionReference),
71 GroupUpdated(GroupUpdated),
72 ReadReceipt(ReadReceipt),
73 WalletSendCalls(WalletSendCalls),
74 Intent(Option<Intent>),
75 Actions(Option<Actions>),
76 LeaveRequest(LeaveRequest),
77 DeletedMessage {
79 deleted_by: DeletedBy,
80 },
81 Custom(EncodedContent),
82}
83
84#[derive(Debug, Clone)]
85pub struct DecodedMessageMetadata {
86 pub id: Vec<u8>,
88 pub group_id: GroupId,
90 pub sent_at_ns: i64,
92 pub kind: GroupMessageKind,
94 pub sender_installation_id: Vec<u8>,
96 pub sender_inbox_id: String,
98 pub delivery_status: DeliveryStatus,
100 pub content_type: ContentTypeId,
102 pub inserted_at_ns: i64,
104 pub expires_at_ns: Option<i64>,
106}
107
108#[derive(Debug, Clone)]
109pub struct DecodedMessage {
110 pub metadata: DecodedMessageMetadata,
111 pub content: MessageBody,
113 pub fallback_text: Option<String>,
115 pub reactions: Vec<DecodedMessage>,
117 pub num_replies: usize,
119}
120
121impl TryFrom<EncodedContent> for MessageBody {
122 type Error = GroupError;
123
124 fn try_from(value: EncodedContent) -> Result<Self, Self::Error> {
125 let content_type = match value.r#type.as_ref() {
126 Some(content_type) => content_type,
127 None => return Err(CodecError::InvalidContentType.into()),
128 };
129
130 match (content_type.type_id.as_str(), content_type.version_major) {
131 (TextCodec::TYPE_ID, TextCodec::MAJOR_VERSION) => {
132 let text = TextCodec::decode(value)?;
133 Ok(MessageBody::Text(Text { content: text }))
134 }
135 (MarkdownCodec::TYPE_ID, MarkdownCodec::MAJOR_VERSION) => {
136 let markdown = MarkdownCodec::decode(value)?;
137 Ok(MessageBody::Markdown(Markdown { content: markdown }))
138 }
139 (AttachmentCodec::TYPE_ID, AttachmentCodec::MAJOR_VERSION) => {
140 let attachment = AttachmentCodec::decode(value)?;
141 Ok(MessageBody::Attachment(attachment))
142 }
143 (RemoteAttachmentCodec::TYPE_ID, RemoteAttachmentCodec::MAJOR_VERSION) => {
144 let remote_attachment = RemoteAttachmentCodec::decode(value)?;
145 Ok(MessageBody::RemoteAttachment(remote_attachment))
146 }
147 (ReplyCodec::TYPE_ID, ReplyCodec::MAJOR_VERSION) => {
148 let reply = ReplyCodec::decode(value)?;
149 let content: MessageBody = reply
152 .content
153 .clone()
154 .try_into()
155 .unwrap_or(MessageBody::Custom(reply.content));
156 Ok(MessageBody::Reply(Reply {
157 in_reply_to: None,
158 content: Box::new(content),
159 reference_id: reply.reference,
160 }))
161 }
162 (ReactionCodec::TYPE_ID, ReactionCodec::MAJOR_VERSION) => {
163 let reaction = ReactionCodec::decode(value)?;
164 Ok(MessageBody::Reaction(reaction))
165 }
166 (LegacyReactionCodec::TYPE_ID, LegacyReactionCodec::MAJOR_VERSION) => {
167 let reaction = LegacyReactionCodec::decode(value)?;
168 Ok(MessageBody::Reaction(reaction.into()))
169 }
170 (MultiRemoteAttachmentCodec::TYPE_ID, MultiRemoteAttachmentCodec::MAJOR_VERSION) => {
171 let multi_remote_attachment = MultiRemoteAttachmentCodec::decode(value)?;
172 Ok(MessageBody::MultiRemoteAttachment(multi_remote_attachment))
173 }
174 (TransactionReferenceCodec::TYPE_ID, TransactionReferenceCodec::MAJOR_VERSION) => {
175 let transaction_reference = TransactionReferenceCodec::decode(value)?;
176 Ok(MessageBody::TransactionReference(transaction_reference))
177 }
178 (GroupUpdatedCodec::TYPE_ID, GroupUpdatedCodec::MAJOR_VERSION) => {
179 let group_updated = GroupUpdatedCodec::decode(value)?;
180 Ok(MessageBody::GroupUpdated(group_updated))
181 }
182 (ReadReceiptCodec::TYPE_ID, ReadReceiptCodec::MAJOR_VERSION) => {
183 let read_receipt = ReadReceiptCodec::decode(value)?;
184 Ok(MessageBody::ReadReceipt(read_receipt))
185 }
186 (WalletSendCallsCodec::TYPE_ID, WalletSendCallsCodec::MAJOR_VERSION) => {
187 let wallet_send_calls = WalletSendCallsCodec::decode(value)?;
188 Ok(MessageBody::WalletSendCalls(wallet_send_calls))
189 }
190 (IntentCodec::TYPE_ID, IntentCodec::MAJOR_VERSION) => {
191 let intent = IntentCodec::decode(value)?;
192 Ok(MessageBody::Intent(Some(intent)))
193 }
194 (ActionsCodec::TYPE_ID, ActionsCodec::MAJOR_VERSION) => {
195 let actions = ActionsCodec::decode(value)?;
196 Ok(MessageBody::Actions(Some(actions)))
197 }
198 (LeaveRequestCodec::TYPE_ID, LeaveRequestCodec::MAJOR_VERSION) => {
199 let leave_request = LeaveRequestCodec::decode(value)?;
200 Ok(MessageBody::LeaveRequest(leave_request))
201 }
202
203 _ => Err(CodecError::CodecNotFound(content_type.clone()).into()),
204 }
205 }
206}
207
208impl TryFrom<StoredGroupMessage> for DecodedMessage {
209 type Error = EnrichMessageError;
210
211 fn try_from(value: StoredGroupMessage) -> Result<Self, Self::Error> {
212 let encoded_content = EncodedContent::decode(&mut value.decrypted_message_bytes.as_slice())
215 .map_err(|_| CodecError::InvalidContentType)?;
216 let content_type_id = encoded_content.r#type.clone().unwrap_or_default();
217 let fallback = encoded_content.fallback.clone();
218
219 let content = match encoded_content.try_into() {
220 Ok(content) => content,
221 Err(_) => MessageBody::Custom(
225 EncodedContent::decode(&mut value.decrypted_message_bytes.as_slice())
226 .map_err(|e| CodecError::Decode(e.to_string()))?,
227 ),
228 };
229
230 let metadata = DecodedMessageMetadata {
232 id: value.id,
233 group_id: value.group_id,
234 sent_at_ns: value.sent_at_ns,
235 kind: value.kind,
236 sender_installation_id: value.sender_installation_id,
237 sender_inbox_id: value.sender_inbox_id,
238 delivery_status: value.delivery_status,
239 content_type: content_type_id,
240 inserted_at_ns: value.inserted_at_ns,
241 expires_at_ns: value.expire_at_ns,
242 };
243
244 let reactions = Vec::new();
247 let num_replies = 0;
248
249 Ok(DecodedMessage {
250 metadata,
251 content,
252 fallback_text: fallback,
253 reactions,
254 num_replies,
255 })
256 }
257}