1use as_variant::as_variant;
6use js_int::Int;
7use ruma_common::{
8 EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedTransactionId,
9 OwnedUserId, RoomId, UserId,
10 canonical_json::RedactionEvent,
11 room_version_rules::RedactionRules,
12 serde::{CanBeEmpty, JsonCastable, JsonObject},
13};
14use ruma_macros::{Event, EventContent};
15use serde::{Deserialize, Serialize};
16use tracing::error;
17
18use crate::{
19 BundledMessageLikeRelations, MessageLikeEventContent, MessageLikeEventType, RedactContent,
20 RedactedMessageLikeEventContent, RedactedUnsigned, StaticEventContent,
21};
22
23mod event_serde;
24
25#[allow(clippy::exhaustive_enums)]
27#[derive(Clone, Debug)]
28pub enum RoomRedactionEvent {
29 Original(OriginalRoomRedactionEvent),
31
32 Redacted(RedactedRoomRedactionEvent),
34}
35
36impl JsonCastable<SyncRoomRedactionEvent> for RoomRedactionEvent {}
37
38impl JsonCastable<JsonObject> for RoomRedactionEvent {}
39
40#[allow(clippy::exhaustive_enums)]
42#[derive(Clone, Debug)]
43pub enum SyncRoomRedactionEvent {
44 Original(OriginalSyncRoomRedactionEvent),
46
47 Redacted(RedactedSyncRoomRedactionEvent),
49}
50
51impl JsonCastable<JsonObject> for SyncRoomRedactionEvent {}
52
53#[derive(Clone, Debug)]
55#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
56pub struct OriginalRoomRedactionEvent {
57 pub content: RoomRedactionEventContent,
59
60 pub redacts: Option<OwnedEventId>,
64
65 pub event_id: OwnedEventId,
67
68 pub sender: OwnedUserId,
70
71 pub origin_server_ts: MilliSecondsSinceUnixEpoch,
73
74 pub room_id: OwnedRoomId,
76
77 pub unsigned: RoomRedactionUnsigned,
79}
80
81impl From<OriginalRoomRedactionEvent> for OriginalSyncRoomRedactionEvent {
82 fn from(value: OriginalRoomRedactionEvent) -> Self {
83 let OriginalRoomRedactionEvent {
84 content,
85 redacts,
86 event_id,
87 sender,
88 origin_server_ts,
89 unsigned,
90 ..
91 } = value;
92
93 Self { content, redacts, event_id, sender, origin_server_ts, unsigned }
94 }
95}
96
97impl JsonCastable<OriginalSyncRoomRedactionEvent> for OriginalRoomRedactionEvent {}
98
99impl JsonCastable<RoomRedactionEvent> for OriginalRoomRedactionEvent {}
100
101impl JsonCastable<SyncRoomRedactionEvent> for OriginalRoomRedactionEvent {}
102
103impl JsonCastable<JsonObject> for OriginalRoomRedactionEvent {}
104
105#[derive(Clone, Debug, Event)]
107#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
108pub struct RedactedRoomRedactionEvent {
109 pub content: RedactedRoomRedactionEventContent,
111
112 pub event_id: OwnedEventId,
114
115 pub sender: OwnedUserId,
117
118 pub origin_server_ts: MilliSecondsSinceUnixEpoch,
120
121 pub room_id: OwnedRoomId,
123
124 pub unsigned: RedactedUnsigned,
126}
127
128impl JsonCastable<RedactedSyncRoomRedactionEvent> for RedactedRoomRedactionEvent {}
129
130impl JsonCastable<RoomRedactionEvent> for RedactedRoomRedactionEvent {}
131
132impl JsonCastable<SyncRoomRedactionEvent> for RedactedRoomRedactionEvent {}
133
134impl JsonCastable<JsonObject> for RedactedRoomRedactionEvent {}
135
136#[derive(Clone, Debug)]
138#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
139pub struct OriginalSyncRoomRedactionEvent {
140 pub content: RoomRedactionEventContent,
142
143 pub redacts: Option<OwnedEventId>,
147
148 pub event_id: OwnedEventId,
150
151 pub sender: OwnedUserId,
153
154 pub origin_server_ts: MilliSecondsSinceUnixEpoch,
156
157 pub unsigned: RoomRedactionUnsigned,
159}
160
161impl OriginalSyncRoomRedactionEvent {
162 pub fn into_full_event(self, room_id: OwnedRoomId) -> OriginalRoomRedactionEvent {
164 let Self { content, redacts, event_id, sender, origin_server_ts, unsigned } = self;
165
166 OriginalRoomRedactionEvent {
167 content,
168 redacts,
169 event_id,
170 sender,
171 origin_server_ts,
172 room_id,
173 unsigned,
174 }
175 }
176
177 pub(crate) fn into_maybe_redacted(self) -> SyncRoomRedactionEvent {
178 SyncRoomRedactionEvent::Original(self)
179 }
180}
181
182impl JsonCastable<SyncRoomRedactionEvent> for OriginalSyncRoomRedactionEvent {}
183
184impl JsonCastable<JsonObject> for OriginalSyncRoomRedactionEvent {}
185
186#[derive(Clone, Debug, Event)]
188#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
189pub struct RedactedSyncRoomRedactionEvent {
190 pub content: RedactedRoomRedactionEventContent,
192
193 pub event_id: OwnedEventId,
195
196 pub sender: OwnedUserId,
198
199 pub origin_server_ts: MilliSecondsSinceUnixEpoch,
201
202 pub unsigned: RedactedUnsigned,
204}
205
206impl JsonCastable<SyncRoomRedactionEvent> for RedactedSyncRoomRedactionEvent {}
207
208impl JsonCastable<JsonObject> for RedactedSyncRoomRedactionEvent {}
209
210#[derive(Clone, Debug, Default, Deserialize, Serialize, EventContent)]
212#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
213#[ruma_event(type = "m.room.redaction", kind = MessageLike, custom_redacted)]
214pub struct RoomRedactionEventContent {
215 #[serde(skip_serializing_if = "Option::is_none")]
219 pub redacts: Option<OwnedEventId>,
220
221 #[serde(skip_serializing_if = "Option::is_none")]
223 pub reason: Option<String>,
224}
225
226impl RoomRedactionEventContent {
227 pub fn new_v1() -> Self {
229 Self::default()
230 }
231
232 pub fn new_v11(redacts: OwnedEventId) -> Self {
235 Self { redacts: Some(redacts), ..Default::default() }
236 }
237
238 pub fn with_reason(mut self, reason: String) -> Self {
240 self.reason = Some(reason);
241 self
242 }
243}
244
245impl RedactContent for RoomRedactionEventContent {
246 type Redacted = RedactedRoomRedactionEventContent;
247
248 fn redact(self, rules: &RedactionRules) -> Self::Redacted {
249 let redacts = self.redacts.filter(|_| rules.keep_room_redaction_redacts);
250 RedactedRoomRedactionEventContent { redacts }
251 }
252}
253
254#[derive(Clone, Debug, Default, Deserialize, Serialize)]
256#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
257pub struct RedactedRoomRedactionEventContent {
258 #[serde(skip_serializing_if = "Option::is_none")]
262 pub redacts: Option<OwnedEventId>,
263}
264
265impl StaticEventContent for RedactedRoomRedactionEventContent {
266 const TYPE: &'static str = RoomRedactionEventContent::TYPE;
267 type IsPrefix = <RoomRedactionEventContent as StaticEventContent>::IsPrefix;
268}
269
270impl RedactedMessageLikeEventContent for RedactedRoomRedactionEventContent {
271 fn event_type(&self) -> MessageLikeEventType {
272 MessageLikeEventType::RoomRedaction
273 }
274}
275
276impl RoomRedactionEvent {
277 pub fn event_type(&self) -> MessageLikeEventType {
279 match self {
280 Self::Original(ev) => ev.content.event_type(),
281 Self::Redacted(ev) => ev.content.event_type(),
282 }
283 }
284
285 pub fn event_id(&self) -> &EventId {
287 match self {
288 Self::Original(ev) => &ev.event_id,
289 Self::Redacted(ev) => &ev.event_id,
290 }
291 }
292
293 pub fn sender(&self) -> &UserId {
295 match self {
296 Self::Original(ev) => &ev.sender,
297 Self::Redacted(ev) => &ev.sender,
298 }
299 }
300
301 pub fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
303 match self {
304 Self::Original(ev) => ev.origin_server_ts,
305 Self::Redacted(ev) => ev.origin_server_ts,
306 }
307 }
308
309 pub fn room_id(&self) -> &RoomId {
311 match self {
312 Self::Original(ev) => &ev.room_id,
313 Self::Redacted(ev) => &ev.room_id,
314 }
315 }
316
317 pub fn redacts(&self, rules: &RedactionRules) -> Option<&EventId> {
324 match self {
325 Self::Original(ev) => Some(ev.redacts(rules)),
326 Self::Redacted(ev) => ev.content.redacts.as_deref(),
327 }
328 }
329
330 pub fn as_original(&self) -> Option<&OriginalRoomRedactionEvent> {
332 as_variant!(self, Self::Original)
333 }
334}
335
336impl SyncRoomRedactionEvent {
337 pub fn event_type(&self) -> MessageLikeEventType {
339 match self {
340 Self::Original(ev) => ev.content.event_type(),
341 Self::Redacted(ev) => ev.content.event_type(),
342 }
343 }
344
345 pub fn event_id(&self) -> &EventId {
347 match self {
348 Self::Original(ev) => &ev.event_id,
349 Self::Redacted(ev) => &ev.event_id,
350 }
351 }
352
353 pub fn sender(&self) -> &UserId {
355 match self {
356 Self::Original(ev) => &ev.sender,
357 Self::Redacted(ev) => &ev.sender,
358 }
359 }
360
361 pub fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
363 match self {
364 Self::Original(ev) => ev.origin_server_ts,
365 Self::Redacted(ev) => ev.origin_server_ts,
366 }
367 }
368
369 pub fn redacts(&self, rules: &RedactionRules) -> Option<&EventId> {
376 match self {
377 Self::Original(ev) => Some(ev.redacts(rules)),
378 Self::Redacted(ev) => ev.content.redacts.as_deref(),
379 }
380 }
381
382 pub fn as_original(&self) -> Option<&OriginalSyncRoomRedactionEvent> {
384 as_variant!(self, Self::Original)
385 }
386
387 pub fn into_full_event(self, room_id: OwnedRoomId) -> RoomRedactionEvent {
389 match self {
390 Self::Original(ev) => RoomRedactionEvent::Original(ev.into_full_event(room_id)),
391 Self::Redacted(ev) => RoomRedactionEvent::Redacted(ev.into_full_event(room_id)),
392 }
393 }
394}
395
396impl From<RoomRedactionEvent> for SyncRoomRedactionEvent {
397 fn from(full: RoomRedactionEvent) -> Self {
398 match full {
399 RoomRedactionEvent::Original(ev) => Self::Original(ev.into()),
400 RoomRedactionEvent::Redacted(ev) => Self::Redacted(ev.into()),
401 }
402 }
403}
404
405impl OriginalRoomRedactionEvent {
406 pub fn redacts(&self, rules: &RedactionRules) -> &EventId {
417 redacts(rules, self.redacts.as_deref(), self.content.redacts.as_deref())
418 }
419}
420
421impl OriginalSyncRoomRedactionEvent {
422 pub fn redacts(&self, rules: &RedactionRules) -> &EventId {
433 redacts(rules, self.redacts.as_deref(), self.content.redacts.as_deref())
434 }
435}
436
437impl RedactionEvent for OriginalRoomRedactionEvent {}
438
439impl RedactionEvent for OriginalSyncRoomRedactionEvent {}
440
441impl RedactionEvent for RoomRedactionEvent {}
442
443impl RedactionEvent for SyncRoomRedactionEvent {}
444
445#[derive(Clone, Debug, Deserialize)]
447#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
448pub struct RoomRedactionUnsigned {
449 pub age: Option<Int>,
455
456 pub transaction_id: Option<OwnedTransactionId>,
459
460 #[serde(rename = "m.relations", default)]
464 pub relations: BundledMessageLikeRelations<OriginalSyncRoomRedactionEvent>,
465}
466
467impl RoomRedactionUnsigned {
468 pub fn new() -> Self {
470 Self { age: None, transaction_id: None, relations: BundledMessageLikeRelations::default() }
471 }
472}
473
474impl Default for RoomRedactionUnsigned {
475 fn default() -> Self {
476 Self::new()
477 }
478}
479
480impl CanBeEmpty for RoomRedactionUnsigned {
481 fn is_empty(&self) -> bool {
487 self.age.is_none() && self.transaction_id.is_none() && self.relations.is_empty()
488 }
489}
490
491fn redacts<'a>(
500 rules: &'_ RedactionRules,
501 redacts: Option<&'a EventId>,
502 content_redacts: Option<&'a EventId>,
503) -> &'a EventId {
504 if rules.content_field_redacts {
505 content_redacts.or_else(|| {
506 error!(
507 "Redacts field inside content not available, \
508 falling back to the one at the event level"
509 );
510 redacts
511 })
512 } else {
513 redacts.or_else(|| {
514 error!(
515 "Redacts field at event level not available, \
516 falling back to the one inside content"
517 );
518 content_redacts
519 })
520 }
521 .expect("At least one redacts field is set")
522}