1use as_variant::as_variant;
6use js_int::Int;
7#[cfg(feature = "canonical-json")]
8use ruma_common::canonical_json::RedactionEvent;
9use ruma_common::{
10 serde::CanBeEmpty, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId,
11 OwnedTransactionId, OwnedUserId, RoomId, RoomVersionId, UserId,
12};
13use ruma_macros::{Event, EventContent};
14use serde::{Deserialize, Serialize};
15use tracing::error;
16
17use crate::{
18 BundledMessageLikeRelations, EventContent, 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, version: &RoomVersionId) -> Self::Redacted {
218 let redacts = match version {
219 RoomVersionId::V1
220 | RoomVersionId::V2
221 | RoomVersionId::V3
222 | RoomVersionId::V4
223 | RoomVersionId::V5
224 | RoomVersionId::V6
225 | RoomVersionId::V7
226 | RoomVersionId::V8
227 | RoomVersionId::V9
228 | RoomVersionId::V10 => None,
229 _ => self.redacts,
230 };
231
232 RedactedRoomRedactionEventContent { redacts }
233 }
234}
235
236#[derive(Clone, Debug, Default, Deserialize, Serialize)]
238#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
239pub struct RedactedRoomRedactionEventContent {
240 #[serde(skip_serializing_if = "Option::is_none")]
244 pub redacts: Option<OwnedEventId>,
245}
246
247impl EventContent for RedactedRoomRedactionEventContent {
248 type EventType = MessageLikeEventType;
249
250 fn event_type(&self) -> Self::EventType {
251 MessageLikeEventType::RoomRedaction
252 }
253}
254
255impl StaticEventContent for RedactedRoomRedactionEventContent {
256 const TYPE: &'static str = "m.room.redaction";
257}
258
259impl RedactedMessageLikeEventContent for RedactedRoomRedactionEventContent {}
260
261impl RoomRedactionEvent {
262 pub fn event_type(&self) -> MessageLikeEventType {
264 match self {
265 Self::Original(ev) => ev.content.event_type(),
266 Self::Redacted(ev) => ev.content.event_type(),
267 }
268 }
269
270 pub fn event_id(&self) -> &EventId {
272 match self {
273 Self::Original(ev) => &ev.event_id,
274 Self::Redacted(ev) => &ev.event_id,
275 }
276 }
277
278 pub fn sender(&self) -> &UserId {
280 match self {
281 Self::Original(ev) => &ev.sender,
282 Self::Redacted(ev) => &ev.sender,
283 }
284 }
285
286 pub fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
288 match self {
289 Self::Original(ev) => ev.origin_server_ts,
290 Self::Redacted(ev) => ev.origin_server_ts,
291 }
292 }
293
294 pub fn room_id(&self) -> &RoomId {
296 match self {
297 Self::Original(ev) => &ev.room_id,
298 Self::Redacted(ev) => &ev.room_id,
299 }
300 }
301
302 pub fn redacts(&self, room_version: &RoomVersionId) -> Option<&EventId> {
309 match self {
310 Self::Original(ev) => Some(ev.redacts(room_version)),
311 Self::Redacted(ev) => ev.content.redacts.as_deref(),
312 }
313 }
314
315 pub fn as_original(&self) -> Option<&OriginalRoomRedactionEvent> {
317 as_variant!(self, Self::Original)
318 }
319}
320
321impl SyncRoomRedactionEvent {
322 pub fn event_type(&self) -> MessageLikeEventType {
324 match self {
325 Self::Original(ev) => ev.content.event_type(),
326 Self::Redacted(ev) => ev.content.event_type(),
327 }
328 }
329
330 pub fn event_id(&self) -> &EventId {
332 match self {
333 Self::Original(ev) => &ev.event_id,
334 Self::Redacted(ev) => &ev.event_id,
335 }
336 }
337
338 pub fn sender(&self) -> &UserId {
340 match self {
341 Self::Original(ev) => &ev.sender,
342 Self::Redacted(ev) => &ev.sender,
343 }
344 }
345
346 pub fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
348 match self {
349 Self::Original(ev) => ev.origin_server_ts,
350 Self::Redacted(ev) => ev.origin_server_ts,
351 }
352 }
353
354 pub fn redacts(&self, room_version: &RoomVersionId) -> Option<&EventId> {
361 match self {
362 Self::Original(ev) => Some(ev.redacts(room_version)),
363 Self::Redacted(ev) => ev.content.redacts.as_deref(),
364 }
365 }
366
367 pub fn as_original(&self) -> Option<&OriginalSyncRoomRedactionEvent> {
369 as_variant!(self, Self::Original)
370 }
371
372 pub fn into_full_event(self, room_id: OwnedRoomId) -> RoomRedactionEvent {
374 match self {
375 Self::Original(ev) => RoomRedactionEvent::Original(ev.into_full_event(room_id)),
376 Self::Redacted(ev) => RoomRedactionEvent::Redacted(ev.into_full_event(room_id)),
377 }
378 }
379}
380
381impl From<RoomRedactionEvent> for SyncRoomRedactionEvent {
382 fn from(full: RoomRedactionEvent) -> Self {
383 match full {
384 RoomRedactionEvent::Original(ev) => Self::Original(ev.into()),
385 RoomRedactionEvent::Redacted(ev) => Self::Redacted(ev.into()),
386 }
387 }
388}
389
390impl OriginalRoomRedactionEvent {
391 pub fn redacts(&self, room_version: &RoomVersionId) -> &EventId {
402 redacts(room_version, self.redacts.as_deref(), self.content.redacts.as_deref())
403 }
404}
405
406impl OriginalSyncRoomRedactionEvent {
407 pub fn redacts(&self, room_version: &RoomVersionId) -> &EventId {
418 redacts(room_version, self.redacts.as_deref(), self.content.redacts.as_deref())
419 }
420}
421
422#[cfg(feature = "canonical-json")]
423impl RedactionEvent for OriginalRoomRedactionEvent {}
424#[cfg(feature = "canonical-json")]
425impl RedactionEvent for OriginalSyncRoomRedactionEvent {}
426#[cfg(feature = "canonical-json")]
427impl RedactionEvent for RoomRedactionEvent {}
428#[cfg(feature = "canonical-json")]
429impl RedactionEvent for SyncRoomRedactionEvent {}
430
431#[derive(Clone, Debug, Deserialize)]
433#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
434pub struct RoomRedactionUnsigned {
435 pub age: Option<Int>,
441
442 pub transaction_id: Option<OwnedTransactionId>,
445
446 #[serde(rename = "m.relations", default)]
450 pub relations: BundledMessageLikeRelations<OriginalSyncRoomRedactionEvent>,
451}
452
453impl RoomRedactionUnsigned {
454 pub fn new() -> Self {
456 Self { age: None, transaction_id: None, relations: BundledMessageLikeRelations::default() }
457 }
458}
459
460impl Default for RoomRedactionUnsigned {
461 fn default() -> Self {
462 Self::new()
463 }
464}
465
466impl CanBeEmpty for RoomRedactionUnsigned {
467 fn is_empty(&self) -> bool {
473 self.age.is_none() && self.transaction_id.is_none() && self.relations.is_empty()
474 }
475}
476
477fn redacts<'a>(
486 room_version: &'_ RoomVersionId,
487 redacts: Option<&'a EventId>,
488 content_redacts: Option<&'a EventId>,
489) -> &'a EventId {
490 match room_version {
491 RoomVersionId::V1
492 | RoomVersionId::V2
493 | RoomVersionId::V3
494 | RoomVersionId::V4
495 | RoomVersionId::V5
496 | RoomVersionId::V6
497 | RoomVersionId::V7
498 | RoomVersionId::V8
499 | RoomVersionId::V9
500 | RoomVersionId::V10 => redacts
501 .or_else(|| {
502 error!("Redacts field at event level not available, falling back to the one inside content");
503 content_redacts
504 })
505 .expect("At least one redacts field is set"),
506 _ => content_redacts
507 .or_else(|| {
508 error!("Redacts field inside content not available, falling back to the one at the event level");
509 redacts
510 })
511 .expect("At least one redacts field is set"),
512 }
513}