matrix_sdk_ui/timeline/event_item/content/
message.rs1use std::fmt;
18
19use ruma::{
20 events::{
21 poll::unstable_start::{
22 NewUnstablePollStartEventContentWithoutRelation, SyncUnstablePollStartEvent,
23 UnstablePollStartEventContent,
24 },
25 room::message::{
26 MessageType, Relation, RoomMessageEventContentWithoutRelation, SyncRoomMessageEvent,
27 },
28 AnySyncMessageLikeEvent, AnySyncTimelineEvent, BundledMessageLikeRelations, Mentions,
29 },
30 html::RemoveReplyFallback,
31 serde::Raw,
32};
33use tracing::{error, trace};
34
35use crate::DEFAULT_SANITIZER_MODE;
36
37#[derive(Clone)]
39pub struct Message {
40 pub(in crate::timeline) msgtype: MessageType,
41 pub(in crate::timeline) edited: bool,
42 pub(in crate::timeline) mentions: Option<Mentions>,
43}
44
45impl Message {
46 pub(in crate::timeline) fn from_event(
48 mut msgtype: MessageType,
49 mentions: Option<Mentions>,
50 edit: Option<RoomMessageEventContentWithoutRelation>,
51 remove_reply_fallback: RemoveReplyFallback,
52 ) -> Self {
53 msgtype.sanitize(DEFAULT_SANITIZER_MODE, remove_reply_fallback);
54
55 let mut ret = Self { msgtype, edited: false, mentions };
56
57 if let Some(edit) = edit {
58 ret.apply_edit(edit);
59 }
60
61 ret
62 }
63
64 pub(crate) fn apply_edit(&mut self, mut new_content: RoomMessageEventContentWithoutRelation) {
66 trace!("applying edit to a Message");
67 new_content.msgtype.sanitize(DEFAULT_SANITIZER_MODE, RemoveReplyFallback::No);
69 self.msgtype = new_content.msgtype;
70 self.mentions = new_content.mentions;
71 self.edited = true;
72 }
73
74 pub fn msgtype(&self) -> &MessageType {
76 &self.msgtype
77 }
78
79 pub fn body(&self) -> &str {
83 self.msgtype.body()
84 }
85
86 pub fn is_edited(&self) -> bool {
89 self.edited
90 }
91
92 pub fn mentions(&self) -> Option<&Mentions> {
94 self.mentions.as_ref()
95 }
96}
97
98pub(crate) fn extract_bundled_edit_event_json(
104 raw: &Raw<AnySyncTimelineEvent>,
105) -> Option<Raw<AnySyncTimelineEvent>> {
106 let raw_unsigned: Raw<serde_json::Value> = raw.get_field("unsigned").ok()??;
108 let raw_relations: Raw<serde_json::Value> = raw_unsigned.get_field("m.relations").ok()??;
109 raw_relations.get_field::<Raw<AnySyncTimelineEvent>>("m.replace").ok()?
110}
111
112pub(crate) fn extract_room_msg_edit_content(
115 relations: BundledMessageLikeRelations<AnySyncMessageLikeEvent>,
116) -> Option<RoomMessageEventContentWithoutRelation> {
117 match *relations.replace? {
118 AnySyncMessageLikeEvent::RoomMessage(SyncRoomMessageEvent::Original(ev)) => match ev
119 .content
120 .relates_to
121 {
122 Some(Relation::Replacement(re)) => {
123 trace!("found a bundled edit event in a room message");
124 Some(re.new_content)
125 }
126 _ => {
127 error!("got m.room.message event with an edit without a valid m.replace relation");
128 None
129 }
130 },
131
132 AnySyncMessageLikeEvent::RoomMessage(SyncRoomMessageEvent::Redacted(_)) => None,
133
134 _ => {
135 error!("got m.room.message event with an edit of a different event type");
136 None
137 }
138 }
139}
140
141pub(crate) fn extract_poll_edit_content(
144 relations: BundledMessageLikeRelations<AnySyncMessageLikeEvent>,
145) -> Option<NewUnstablePollStartEventContentWithoutRelation> {
146 match *relations.replace? {
147 AnySyncMessageLikeEvent::UnstablePollStart(SyncUnstablePollStartEvent::Original(ev)) => {
148 match ev.content {
149 UnstablePollStartEventContent::Replacement(re) => {
150 trace!("found a bundled edit event in a poll");
151 Some(re.relates_to.new_content)
152 }
153 _ => {
154 error!("got new poll start event in a bundled edit");
155 None
156 }
157 }
158 }
159
160 AnySyncMessageLikeEvent::UnstablePollStart(SyncUnstablePollStartEvent::Redacted(_)) => None,
161
162 _ => {
163 error!("got poll edit event with an edit of a different event type");
164 None
165 }
166 }
167}
168
169#[cfg(not(tarpaulin_include))]
170impl fmt::Debug for Message {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 let Self { msgtype: _, edited, mentions: _ } = self;
173 f.debug_struct("Message").field("edited", edited).finish_non_exhaustive()
176 }
177}