Skip to main content

tuwunel_core/matrix/event/
redact.rs

1use ruma::{
2	OwnedEventId,
3	events::{TimelineEventType, room::redaction::RoomRedactionEventContent},
4	room_version_rules::RoomVersionRules,
5};
6use serde::Deserialize;
7use serde_json::value::{RawValue as RawJsonValue, to_raw_value};
8
9use super::Event;
10
11/// Copies the `redacts` property of the event to the `content` dict and
12/// vice-versa.
13///
14/// This follows the specification's
15/// [recommendation](https://spec.matrix.org/v1.10/rooms/v11/#moving-the-redacts-property-of-mroomredaction-events-to-a-content-property):
16///
17/// > For backwards-compatibility with older clients, servers should add a
18/// > redacts property to the top level of m.room.redaction events in when
19/// > serving such events over the Client-Server API.
20///
21/// > For improved compatibility with newer clients, servers should add a
22/// > redacts property to the content of m.room.redaction events in older
23/// > room versions when serving such events over the Client-Server API.
24#[must_use]
25pub(super) fn copy<E: Event>(event: &E) -> (Option<OwnedEventId>, Box<RawJsonValue>) {
26	if *event.event_type() != TimelineEventType::RoomRedaction {
27		return (event.redacts().map(ToOwned::to_owned), event.content().to_owned());
28	}
29
30	let Ok(mut content) = event.get_content::<RoomRedactionEventContent>() else {
31		return (event.redacts().map(ToOwned::to_owned), event.content().to_owned());
32	};
33
34	if let Some(redacts) = content.redacts {
35		return (Some(redacts), event.content().to_owned());
36	}
37
38	if let Some(redacts) = event.redacts().map(ToOwned::to_owned) {
39		content.redacts = Some(redacts);
40		return (
41			event.redacts().map(ToOwned::to_owned),
42			to_raw_value(&content).expect("Must be valid, we only added redacts field"),
43		);
44	}
45
46	(event.redacts().map(ToOwned::to_owned), event.content().to_owned())
47}
48
49#[must_use]
50pub(super) fn is_redacted<E: Event>(event: &E) -> bool {
51	let Some(unsigned) = event.unsigned() else {
52		return false;
53	};
54
55	let Ok(unsigned) = ExtractRedactedBecause::deserialize(unsigned) else {
56		return false;
57	};
58
59	unsigned.redacted_because.is_some()
60}
61
62#[must_use]
63pub(super) fn redacts_id<E: Event>(
64	event: &E,
65	room_rules: &RoomVersionRules,
66) -> Option<OwnedEventId> {
67	if *event.kind() != TimelineEventType::RoomRedaction {
68		return None;
69	}
70
71	if room_rules.redaction.content_field_redacts {
72		event
73			.get_content::<RoomRedactionEventContent>()
74			.ok()?
75			.redacts
76	} else {
77		event.redacts().map(ToOwned::to_owned)
78	}
79}
80
81#[derive(Deserialize)]
82struct ExtractRedactedBecause {
83	redacted_because: Option<serde::de::IgnoredAny>,
84}