1use as_variant::as_variant;
6use js_int::Int;
7#[cfg(feature = "canonical-json")]
8use ruma_common::canonical_json::RedactionEvent;
9use ruma_common::{
10 room_version_rules::RedactionRules, serde::CanBeEmpty, EventId, MilliSecondsSinceUnixEpoch,
11 OwnedEventId, OwnedRoomId, OwnedTransactionId, OwnedUserId, RoomId, UserId,
12};
13use ruma_macros::{Event, EventContent};
14use serde::{Deserialize, Serialize};
15use tracing::error;
16
17use crate::{
18 BundledMessageLikeRelations, MessageLikeEventContent, MessageLikeEventType, RedactContent,
19 RedactedMessageLikeEventContent, RedactedUnsigned, StaticEventContent,
20};
21
22mod event_serde;
23
24#[allow(clippy::exhaustive_enums)]
26#[derive(Clone, Debug)]
27pub enum RoomRedactionEvent {
28 Original(OriginalRoomRedactionEvent),
30
31 Redacted(RedactedRoomRedactionEvent),
33}
34
35#[allow(clippy::exhaustive_enums)]
37#[derive(Clone, Debug)]
38pub enum SyncRoomRedactionEvent {
39 Original(OriginalSyncRoomRedactionEvent),
41
42 Redacted(RedactedSyncRoomRedactionEvent),
44}
45
46#[derive(Clone, Debug)]
48#[allow(clippy::exhaustive_structs)]
49pub struct OriginalRoomRedactionEvent {
50 pub content: RoomRedactionEventContent,
52
53 pub redacts: Option<OwnedEventId>,
57
58 pub event_id: OwnedEventId,
60
61 pub sender: OwnedUserId,
63
64 pub origin_server_ts: MilliSecondsSinceUnixEpoch,
66
67 pub room_id: OwnedRoomId,
69
70 pub unsigned: RoomRedactionUnsigned,
72}
73
74impl From<OriginalRoomRedactionEvent> for OriginalSyncRoomRedactionEvent {
75 fn from(value: OriginalRoomRedactionEvent) -> Self {
76 let OriginalRoomRedactionEvent {
77 content,
78 redacts,
79 event_id,
80 sender,
81 origin_server_ts,
82 unsigned,
83 ..
84 } = value;
85
86 Self { content, redacts, event_id, sender, origin_server_ts, unsigned }
87 }
88}
89
90#[derive(Clone, Debug, Event)]
92#[allow(clippy::exhaustive_structs)]
93pub struct RedactedRoomRedactionEvent {
94 pub content: RedactedRoomRedactionEventContent,
96
97 pub event_id: OwnedEventId,
99
100 pub sender: OwnedUserId,
102
103 pub origin_server_ts: MilliSecondsSinceUnixEpoch,
105
106 pub room_id: OwnedRoomId,
108
109 pub unsigned: RedactedUnsigned,
111}
112
113#[derive(Clone, Debug)]
115#[allow(clippy::exhaustive_structs)]
116pub struct OriginalSyncRoomRedactionEvent {
117 pub content: RoomRedactionEventContent,
119
120 pub redacts: Option<OwnedEventId>,
124
125 pub event_id: OwnedEventId,
127
128 pub sender: OwnedUserId,
130
131 pub origin_server_ts: MilliSecondsSinceUnixEpoch,
133
134 pub unsigned: RoomRedactionUnsigned,
136}
137
138impl OriginalSyncRoomRedactionEvent {
139 pub fn into_full_event(self, room_id: OwnedRoomId) -> OriginalRoomRedactionEvent {
141 let Self { content, redacts, event_id, sender, origin_server_ts, unsigned } = self;
142
143 OriginalRoomRedactionEvent {
144 content,
145 redacts,
146 event_id,
147 sender,
148 origin_server_ts,
149 room_id,
150 unsigned,
151 }
152 }
153
154 pub(crate) fn into_maybe_redacted(self) -> SyncRoomRedactionEvent {
155 SyncRoomRedactionEvent::Original(self)
156 }
157}
158
159#[derive(Clone, Debug, Event)]
161#[allow(clippy::exhaustive_structs)]
162pub struct RedactedSyncRoomRedactionEvent {
163 pub content: RedactedRoomRedactionEventContent,
165
166 pub event_id: OwnedEventId,
168
169 pub sender: OwnedUserId,
171
172 pub origin_server_ts: MilliSecondsSinceUnixEpoch,
174
175 pub unsigned: RedactedUnsigned,
177}
178
179#[derive(Clone, Debug, Default, Deserialize, Serialize, EventContent)]
181#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
182#[ruma_event(type = "m.room.redaction", kind = MessageLike, custom_redacted)]
183pub struct RoomRedactionEventContent {
184 #[serde(skip_serializing_if = "Option::is_none")]
188 pub redacts: Option<OwnedEventId>,
189
190 #[serde(skip_serializing_if = "Option::is_none")]
192 pub reason: Option<String>,
193}
194
195impl RoomRedactionEventContent {
196 pub fn new_v1() -> Self {
198 Self::default()
199 }
200
201 pub fn new_v11(redacts: OwnedEventId) -> Self {
204 Self { redacts: Some(redacts), ..Default::default() }
205 }
206
207 pub fn with_reason(mut self, reason: String) -> Self {
209 self.reason = Some(reason);
210 self
211 }
212}
213
214impl RedactContent for RoomRedactionEventContent {
215 type Redacted = RedactedRoomRedactionEventContent;
216
217 fn redact(self, rules: &RedactionRules) -> Self::Redacted {
218 let redacts = self.redacts.filter(|_| rules.keep_room_redaction_redacts);
219 RedactedRoomRedactionEventContent { redacts }
220 }
221}
222
223#[derive(Clone, Debug, Default, Deserialize, Serialize)]
225#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
226pub struct RedactedRoomRedactionEventContent {
227 #[serde(skip_serializing_if = "Option::is_none")]
231 pub redacts: Option<OwnedEventId>,
232}
233
234impl StaticEventContent for RedactedRoomRedactionEventContent {
235 const TYPE: &'static str = RoomRedactionEventContent::TYPE;
236 type IsPrefix = <RoomRedactionEventContent as StaticEventContent>::IsPrefix;
237}
238
239impl RedactedMessageLikeEventContent for RedactedRoomRedactionEventContent {
240 fn event_type(&self) -> MessageLikeEventType {
241 MessageLikeEventType::RoomRedaction
242 }
243}
244
245impl RoomRedactionEvent {
246 pub fn event_type(&self) -> MessageLikeEventType {
248 match self {
249 Self::Original(ev) => ev.content.event_type(),
250 Self::Redacted(ev) => ev.content.event_type(),
251 }
252 }
253
254 pub fn event_id(&self) -> &EventId {
256 match self {
257 Self::Original(ev) => &ev.event_id,
258 Self::Redacted(ev) => &ev.event_id,
259 }
260 }
261
262 pub fn sender(&self) -> &UserId {
264 match self {
265 Self::Original(ev) => &ev.sender,
266 Self::Redacted(ev) => &ev.sender,
267 }
268 }
269
270 pub fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
272 match self {
273 Self::Original(ev) => ev.origin_server_ts,
274 Self::Redacted(ev) => ev.origin_server_ts,
275 }
276 }
277
278 pub fn room_id(&self) -> &RoomId {
280 match self {
281 Self::Original(ev) => &ev.room_id,
282 Self::Redacted(ev) => &ev.room_id,
283 }
284 }
285
286 pub fn redacts(&self, rules: &RedactionRules) -> Option<&EventId> {
293 match self {
294 Self::Original(ev) => Some(ev.redacts(rules)),
295 Self::Redacted(ev) => ev.content.redacts.as_deref(),
296 }
297 }
298
299 pub fn as_original(&self) -> Option<&OriginalRoomRedactionEvent> {
301 as_variant!(self, Self::Original)
302 }
303}
304
305impl SyncRoomRedactionEvent {
306 pub fn event_type(&self) -> MessageLikeEventType {
308 match self {
309 Self::Original(ev) => ev.content.event_type(),
310 Self::Redacted(ev) => ev.content.event_type(),
311 }
312 }
313
314 pub fn event_id(&self) -> &EventId {
316 match self {
317 Self::Original(ev) => &ev.event_id,
318 Self::Redacted(ev) => &ev.event_id,
319 }
320 }
321
322 pub fn sender(&self) -> &UserId {
324 match self {
325 Self::Original(ev) => &ev.sender,
326 Self::Redacted(ev) => &ev.sender,
327 }
328 }
329
330 pub fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
332 match self {
333 Self::Original(ev) => ev.origin_server_ts,
334 Self::Redacted(ev) => ev.origin_server_ts,
335 }
336 }
337
338 pub fn redacts(&self, rules: &RedactionRules) -> Option<&EventId> {
345 match self {
346 Self::Original(ev) => Some(ev.redacts(rules)),
347 Self::Redacted(ev) => ev.content.redacts.as_deref(),
348 }
349 }
350
351 pub fn as_original(&self) -> Option<&OriginalSyncRoomRedactionEvent> {
353 as_variant!(self, Self::Original)
354 }
355
356 pub fn into_full_event(self, room_id: OwnedRoomId) -> RoomRedactionEvent {
358 match self {
359 Self::Original(ev) => RoomRedactionEvent::Original(ev.into_full_event(room_id)),
360 Self::Redacted(ev) => RoomRedactionEvent::Redacted(ev.into_full_event(room_id)),
361 }
362 }
363}
364
365impl From<RoomRedactionEvent> for SyncRoomRedactionEvent {
366 fn from(full: RoomRedactionEvent) -> Self {
367 match full {
368 RoomRedactionEvent::Original(ev) => Self::Original(ev.into()),
369 RoomRedactionEvent::Redacted(ev) => Self::Redacted(ev.into()),
370 }
371 }
372}
373
374impl OriginalRoomRedactionEvent {
375 pub fn redacts(&self, rules: &RedactionRules) -> &EventId {
386 redacts(rules, self.redacts.as_deref(), self.content.redacts.as_deref())
387 }
388}
389
390impl OriginalSyncRoomRedactionEvent {
391 pub fn redacts(&self, rules: &RedactionRules) -> &EventId {
402 redacts(rules, self.redacts.as_deref(), self.content.redacts.as_deref())
403 }
404}
405
406#[cfg(feature = "canonical-json")]
407impl RedactionEvent for OriginalRoomRedactionEvent {}
408#[cfg(feature = "canonical-json")]
409impl RedactionEvent for OriginalSyncRoomRedactionEvent {}
410#[cfg(feature = "canonical-json")]
411impl RedactionEvent for RoomRedactionEvent {}
412#[cfg(feature = "canonical-json")]
413impl RedactionEvent for SyncRoomRedactionEvent {}
414
415#[derive(Clone, Debug, Deserialize)]
417#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
418pub struct RoomRedactionUnsigned {
419 pub age: Option<Int>,
425
426 pub transaction_id: Option<OwnedTransactionId>,
429
430 #[serde(rename = "m.relations", default)]
434 pub relations: BundledMessageLikeRelations<OriginalSyncRoomRedactionEvent>,
435}
436
437impl RoomRedactionUnsigned {
438 pub fn new() -> Self {
440 Self { age: None, transaction_id: None, relations: BundledMessageLikeRelations::default() }
441 }
442}
443
444impl Default for RoomRedactionUnsigned {
445 fn default() -> Self {
446 Self::new()
447 }
448}
449
450impl CanBeEmpty for RoomRedactionUnsigned {
451 fn is_empty(&self) -> bool {
457 self.age.is_none() && self.transaction_id.is_none() && self.relations.is_empty()
458 }
459}
460
461fn redacts<'a>(
470 rules: &'_ RedactionRules,
471 redacts: Option<&'a EventId>,
472 content_redacts: Option<&'a EventId>,
473) -> &'a EventId {
474 if rules.content_field_redacts {
475 content_redacts.or_else(|| {
476 error!(
477 "Redacts field inside content not available, \
478 falling back to the one at the event level"
479 );
480 redacts
481 })
482 } else {
483 redacts.or_else(|| {
484 error!(
485 "Redacts field at event level not available, \
486 falling back to the one inside content"
487 );
488 content_redacts
489 })
490 }
491 .expect("At least one redacts field is set")
492}