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,
11 serde::{CanBeEmpty, JsonCastable, JsonObject},
12 EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedTransactionId,
13 OwnedUserId, RoomId, UserId,
14};
15use ruma_macros::{Event, EventContent};
16use serde::{Deserialize, Serialize};
17use tracing::error;
18
19use crate::{
20 BundledMessageLikeRelations, MessageLikeEventContent, MessageLikeEventType, RedactContent,
21 RedactedMessageLikeEventContent, RedactedUnsigned, StaticEventContent,
22};
23
24mod event_serde;
25
26#[allow(clippy::exhaustive_enums)]
28#[derive(Clone, Debug)]
29pub enum RoomRedactionEvent {
30 Original(OriginalRoomRedactionEvent),
32
33 Redacted(RedactedRoomRedactionEvent),
35}
36
37impl JsonCastable<SyncRoomRedactionEvent> for RoomRedactionEvent {}
38
39impl JsonCastable<JsonObject> for RoomRedactionEvent {}
40
41#[allow(clippy::exhaustive_enums)]
43#[derive(Clone, Debug)]
44pub enum SyncRoomRedactionEvent {
45 Original(OriginalSyncRoomRedactionEvent),
47
48 Redacted(RedactedSyncRoomRedactionEvent),
50}
51
52impl JsonCastable<JsonObject> for SyncRoomRedactionEvent {}
53
54#[derive(Clone, Debug)]
56#[allow(clippy::exhaustive_structs)]
57pub struct OriginalRoomRedactionEvent {
58 pub content: RoomRedactionEventContent,
60
61 pub redacts: Option<OwnedEventId>,
65
66 pub event_id: OwnedEventId,
68
69 pub sender: OwnedUserId,
71
72 pub origin_server_ts: MilliSecondsSinceUnixEpoch,
74
75 pub room_id: OwnedRoomId,
77
78 pub unsigned: RoomRedactionUnsigned,
80}
81
82impl From<OriginalRoomRedactionEvent> for OriginalSyncRoomRedactionEvent {
83 fn from(value: OriginalRoomRedactionEvent) -> Self {
84 let OriginalRoomRedactionEvent {
85 content,
86 redacts,
87 event_id,
88 sender,
89 origin_server_ts,
90 unsigned,
91 ..
92 } = value;
93
94 Self { content, redacts, event_id, sender, origin_server_ts, unsigned }
95 }
96}
97
98impl JsonCastable<OriginalSyncRoomRedactionEvent> for OriginalRoomRedactionEvent {}
99
100impl JsonCastable<RoomRedactionEvent> for OriginalRoomRedactionEvent {}
101
102impl JsonCastable<SyncRoomRedactionEvent> for OriginalRoomRedactionEvent {}
103
104impl JsonCastable<JsonObject> for OriginalRoomRedactionEvent {}
105
106#[derive(Clone, Debug, Event)]
108#[allow(clippy::exhaustive_structs)]
109pub struct RedactedRoomRedactionEvent {
110 pub content: RedactedRoomRedactionEventContent,
112
113 pub event_id: OwnedEventId,
115
116 pub sender: OwnedUserId,
118
119 pub origin_server_ts: MilliSecondsSinceUnixEpoch,
121
122 pub room_id: OwnedRoomId,
124
125 pub unsigned: RedactedUnsigned,
127}
128
129impl JsonCastable<RedactedSyncRoomRedactionEvent> for RedactedRoomRedactionEvent {}
130
131impl JsonCastable<RoomRedactionEvent> for RedactedRoomRedactionEvent {}
132
133impl JsonCastable<SyncRoomRedactionEvent> for RedactedRoomRedactionEvent {}
134
135impl JsonCastable<JsonObject> for RedactedRoomRedactionEvent {}
136
137#[derive(Clone, Debug)]
139#[allow(clippy::exhaustive_structs)]
140pub struct OriginalSyncRoomRedactionEvent {
141 pub content: RoomRedactionEventContent,
143
144 pub redacts: Option<OwnedEventId>,
148
149 pub event_id: OwnedEventId,
151
152 pub sender: OwnedUserId,
154
155 pub origin_server_ts: MilliSecondsSinceUnixEpoch,
157
158 pub unsigned: RoomRedactionUnsigned,
160}
161
162impl OriginalSyncRoomRedactionEvent {
163 pub fn into_full_event(self, room_id: OwnedRoomId) -> OriginalRoomRedactionEvent {
165 let Self { content, redacts, event_id, sender, origin_server_ts, unsigned } = self;
166
167 OriginalRoomRedactionEvent {
168 content,
169 redacts,
170 event_id,
171 sender,
172 origin_server_ts,
173 room_id,
174 unsigned,
175 }
176 }
177
178 pub(crate) fn into_maybe_redacted(self) -> SyncRoomRedactionEvent {
179 SyncRoomRedactionEvent::Original(self)
180 }
181}
182
183impl JsonCastable<SyncRoomRedactionEvent> for OriginalSyncRoomRedactionEvent {}
184
185impl JsonCastable<JsonObject> for OriginalSyncRoomRedactionEvent {}
186
187#[derive(Clone, Debug, Event)]
189#[allow(clippy::exhaustive_structs)]
190pub struct RedactedSyncRoomRedactionEvent {
191 pub content: RedactedRoomRedactionEventContent,
193
194 pub event_id: OwnedEventId,
196
197 pub sender: OwnedUserId,
199
200 pub origin_server_ts: MilliSecondsSinceUnixEpoch,
202
203 pub unsigned: RedactedUnsigned,
205}
206
207impl JsonCastable<SyncRoomRedactionEvent> for RedactedSyncRoomRedactionEvent {}
208
209impl JsonCastable<JsonObject> for RedactedSyncRoomRedactionEvent {}
210
211#[derive(Clone, Debug, Default, Deserialize, Serialize, EventContent)]
213#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
214#[ruma_event(type = "m.room.redaction", kind = MessageLike, custom_redacted)]
215pub struct RoomRedactionEventContent {
216 #[serde(skip_serializing_if = "Option::is_none")]
220 pub redacts: Option<OwnedEventId>,
221
222 #[serde(skip_serializing_if = "Option::is_none")]
224 pub reason: Option<String>,
225}
226
227impl RoomRedactionEventContent {
228 pub fn new_v1() -> Self {
230 Self::default()
231 }
232
233 pub fn new_v11(redacts: OwnedEventId) -> Self {
236 Self { redacts: Some(redacts), ..Default::default() }
237 }
238
239 pub fn with_reason(mut self, reason: String) -> Self {
241 self.reason = Some(reason);
242 self
243 }
244}
245
246impl RedactContent for RoomRedactionEventContent {
247 type Redacted = RedactedRoomRedactionEventContent;
248
249 fn redact(self, rules: &RedactionRules) -> Self::Redacted {
250 let redacts = self.redacts.filter(|_| rules.keep_room_redaction_redacts);
251 RedactedRoomRedactionEventContent { redacts }
252 }
253}
254
255#[derive(Clone, Debug, Default, Deserialize, Serialize)]
257#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
258pub struct RedactedRoomRedactionEventContent {
259 #[serde(skip_serializing_if = "Option::is_none")]
263 pub redacts: Option<OwnedEventId>,
264}
265
266impl StaticEventContent for RedactedRoomRedactionEventContent {
267 const TYPE: &'static str = RoomRedactionEventContent::TYPE;
268 type IsPrefix = <RoomRedactionEventContent as StaticEventContent>::IsPrefix;
269}
270
271impl RedactedMessageLikeEventContent for RedactedRoomRedactionEventContent {
272 fn event_type(&self) -> MessageLikeEventType {
273 MessageLikeEventType::RoomRedaction
274 }
275}
276
277impl RoomRedactionEvent {
278 pub fn event_type(&self) -> MessageLikeEventType {
280 match self {
281 Self::Original(ev) => ev.content.event_type(),
282 Self::Redacted(ev) => ev.content.event_type(),
283 }
284 }
285
286 pub fn event_id(&self) -> &EventId {
288 match self {
289 Self::Original(ev) => &ev.event_id,
290 Self::Redacted(ev) => &ev.event_id,
291 }
292 }
293
294 pub fn sender(&self) -> &UserId {
296 match self {
297 Self::Original(ev) => &ev.sender,
298 Self::Redacted(ev) => &ev.sender,
299 }
300 }
301
302 pub fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
304 match self {
305 Self::Original(ev) => ev.origin_server_ts,
306 Self::Redacted(ev) => ev.origin_server_ts,
307 }
308 }
309
310 pub fn room_id(&self) -> &RoomId {
312 match self {
313 Self::Original(ev) => &ev.room_id,
314 Self::Redacted(ev) => &ev.room_id,
315 }
316 }
317
318 pub fn redacts(&self, rules: &RedactionRules) -> Option<&EventId> {
325 match self {
326 Self::Original(ev) => Some(ev.redacts(rules)),
327 Self::Redacted(ev) => ev.content.redacts.as_deref(),
328 }
329 }
330
331 pub fn as_original(&self) -> Option<&OriginalRoomRedactionEvent> {
333 as_variant!(self, Self::Original)
334 }
335}
336
337impl SyncRoomRedactionEvent {
338 pub fn event_type(&self) -> MessageLikeEventType {
340 match self {
341 Self::Original(ev) => ev.content.event_type(),
342 Self::Redacted(ev) => ev.content.event_type(),
343 }
344 }
345
346 pub fn event_id(&self) -> &EventId {
348 match self {
349 Self::Original(ev) => &ev.event_id,
350 Self::Redacted(ev) => &ev.event_id,
351 }
352 }
353
354 pub fn sender(&self) -> &UserId {
356 match self {
357 Self::Original(ev) => &ev.sender,
358 Self::Redacted(ev) => &ev.sender,
359 }
360 }
361
362 pub fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
364 match self {
365 Self::Original(ev) => ev.origin_server_ts,
366 Self::Redacted(ev) => ev.origin_server_ts,
367 }
368 }
369
370 pub fn redacts(&self, rules: &RedactionRules) -> Option<&EventId> {
377 match self {
378 Self::Original(ev) => Some(ev.redacts(rules)),
379 Self::Redacted(ev) => ev.content.redacts.as_deref(),
380 }
381 }
382
383 pub fn as_original(&self) -> Option<&OriginalSyncRoomRedactionEvent> {
385 as_variant!(self, Self::Original)
386 }
387
388 pub fn into_full_event(self, room_id: OwnedRoomId) -> RoomRedactionEvent {
390 match self {
391 Self::Original(ev) => RoomRedactionEvent::Original(ev.into_full_event(room_id)),
392 Self::Redacted(ev) => RoomRedactionEvent::Redacted(ev.into_full_event(room_id)),
393 }
394 }
395}
396
397impl From<RoomRedactionEvent> for SyncRoomRedactionEvent {
398 fn from(full: RoomRedactionEvent) -> Self {
399 match full {
400 RoomRedactionEvent::Original(ev) => Self::Original(ev.into()),
401 RoomRedactionEvent::Redacted(ev) => Self::Redacted(ev.into()),
402 }
403 }
404}
405
406impl OriginalRoomRedactionEvent {
407 pub fn redacts(&self, rules: &RedactionRules) -> &EventId {
418 redacts(rules, self.redacts.as_deref(), self.content.redacts.as_deref())
419 }
420}
421
422impl OriginalSyncRoomRedactionEvent {
423 pub fn redacts(&self, rules: &RedactionRules) -> &EventId {
434 redacts(rules, self.redacts.as_deref(), self.content.redacts.as_deref())
435 }
436}
437
438#[cfg(feature = "canonical-json")]
439impl RedactionEvent for OriginalRoomRedactionEvent {}
440#[cfg(feature = "canonical-json")]
441impl RedactionEvent for OriginalSyncRoomRedactionEvent {}
442#[cfg(feature = "canonical-json")]
443impl RedactionEvent for RoomRedactionEvent {}
444#[cfg(feature = "canonical-json")]
445impl RedactionEvent for SyncRoomRedactionEvent {}
446
447#[derive(Clone, Debug, Deserialize)]
449#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
450pub struct RoomRedactionUnsigned {
451 pub age: Option<Int>,
457
458 pub transaction_id: Option<OwnedTransactionId>,
461
462 #[serde(rename = "m.relations", default)]
466 pub relations: BundledMessageLikeRelations<OriginalSyncRoomRedactionEvent>,
467}
468
469impl RoomRedactionUnsigned {
470 pub fn new() -> Self {
472 Self { age: None, transaction_id: None, relations: BundledMessageLikeRelations::default() }
473 }
474}
475
476impl Default for RoomRedactionUnsigned {
477 fn default() -> Self {
478 Self::new()
479 }
480}
481
482impl CanBeEmpty for RoomRedactionUnsigned {
483 fn is_empty(&self) -> bool {
489 self.age.is_none() && self.transaction_id.is_none() && self.relations.is_empty()
490 }
491}
492
493fn redacts<'a>(
502 rules: &'_ RedactionRules,
503 redacts: Option<&'a EventId>,
504 content_redacts: Option<&'a EventId>,
505) -> &'a EventId {
506 if rules.content_field_redacts {
507 content_redacts.or_else(|| {
508 error!(
509 "Redacts field inside content not available, \
510 falling back to the one at the event level"
511 );
512 redacts
513 })
514 } else {
515 redacts.or_else(|| {
516 error!(
517 "Redacts field at event level not available, \
518 falling back to the one inside content"
519 );
520 content_redacts
521 })
522 }
523 .expect("At least one redacts field is set")
524}