use ruma_common::MilliSecondsSinceUnixEpoch; use ruma_events_macros::{event_enum, EventEnumFromEvent}; use ruma_identifiers::{EventId, RoomId, RoomVersionId, UserId}; use serde::{de, Deserialize, Serialize}; use serde_json::value::RawValue as RawJsonValue; use crate::{ from_raw_json_value, room::redaction::SyncRoomRedactionEvent, Redact, UnsignedDeHelper, }; event_enum! { /// Any global account data event. enum GlobalAccountData { "m.direct", "m.ignored_user_list", "m.push_rules", } /// Any room account data event. enum RoomAccountData { "m.fully_read", "m.tag", } /// Any ephemeral room event. enum EphemeralRoom { "m.receipt", "m.typing", } /// Any message event. enum Message { "m.call.answer", "m.call.invite", "m.call.hangup", "m.call.candidates", #[cfg(feature = "unstable-pre-spec")] "m.key.verification.ready", #[cfg(feature = "unstable-pre-spec")] "m.key.verification.start", #[cfg(feature = "unstable-pre-spec")] "m.key.verification.cancel", #[cfg(feature = "unstable-pre-spec")] "m.key.verification.accept", #[cfg(feature = "unstable-pre-spec")] "m.key.verification.key", #[cfg(feature = "unstable-pre-spec")] "m.key.verification.mac", #[cfg(feature = "unstable-pre-spec")] "m.key.verification.done", #[cfg(feature = "unstable-pre-spec")] "m.reaction", "m.room.encrypted", "m.room.message", "m.room.message.feedback", "m.room.redaction", "m.sticker", } /// Any state event. enum State { "m.policy.rule.room", "m.policy.rule.server", "m.policy.rule.user", "m.room.aliases", "m.room.avatar", "m.room.canonical_alias", "m.room.create", "m.room.encryption", "m.room.guest_access", "m.room.history_visibility", "m.room.join_rules", "m.room.member", "m.room.name", "m.room.pinned_events", "m.room.power_levels", "m.room.server_acl", "m.room.third_party_invite", "m.room.tombstone", "m.room.topic", #[cfg(feature = "unstable-pre-spec")] "m.space.child", #[cfg(feature = "unstable-pre-spec")] "m.space.parent", } /// Any to-device event. enum ToDevice { "m.dummy", "m.room_key", "m.room_key_request", "m.forwarded_room_key", "m.key.verification.request", #[cfg(feature = "unstable-pre-spec")] "m.key.verification.ready", "m.key.verification.start", "m.key.verification.cancel", "m.key.verification.accept", "m.key.verification.key", "m.key.verification.mac", #[cfg(feature = "unstable-pre-spec")] "m.key.verification.done", "m.room.encrypted", #[cfg(feature = "unstable-pre-spec")] "m.secret.request", #[cfg(feature = "unstable-pre-spec")] "m.secret.send", } } /// Declares an item with a doc attribute computed by some macro expression. /// This allows documentation to be dynamically generated based on input. /// Necessary to work around . macro_rules! doc_concat { ( $( #[doc = $doc:expr] $( $thing:tt )* )* ) => ( $( #[doc = $doc] $( $thing )* )* ); } macro_rules! room_ev_accessor { ($field:ident: $ty:ty) => { doc_concat! { #[doc = concat!("Returns this event's `", stringify!($field), "` field.")] pub fn $field(&self) -> $ty { match self { Self::Message(ev) => ev.$field(), Self::State(ev) => ev.$field(), Self::RedactedMessage(ev) => ev.$field(), Self::RedactedState(ev) => ev.$field(), } } } }; } /// Any room event. #[allow(clippy::large_enum_variant, clippy::exhaustive_enums)] #[derive(Clone, Debug, Serialize, EventEnumFromEvent)] #[serde(untagged)] pub enum AnyRoomEvent { /// Any message event. Message(AnyMessageEvent), /// Any state event. State(AnyStateEvent), /// Any message event that has been redacted. RedactedMessage(AnyRedactedMessageEvent), /// Any state event that has been redacted. RedactedState(AnyRedactedStateEvent), } impl AnyRoomEvent { room_ev_accessor!(origin_server_ts: &MilliSecondsSinceUnixEpoch); room_ev_accessor!(room_id: &RoomId); room_ev_accessor!(event_id: &EventId); room_ev_accessor!(sender: &UserId); } /// Any sync room event. /// /// Sync room events are room event without a `room_id`, as returned in `/sync` responses. #[allow(clippy::large_enum_variant, clippy::exhaustive_enums)] #[derive(Clone, Debug, Serialize, EventEnumFromEvent)] #[serde(untagged)] pub enum AnySyncRoomEvent { /// Any sync message event. Message(AnySyncMessageEvent), /// Any sync state event. State(AnySyncStateEvent), /// Any sync message event that has been redacted. RedactedMessage(AnyRedactedSyncMessageEvent), /// Any sync state event that has been redacted. RedactedState(AnyRedactedSyncStateEvent), } impl AnySyncRoomEvent { room_ev_accessor!(origin_server_ts: &MilliSecondsSinceUnixEpoch); room_ev_accessor!(event_id: &EventId); room_ev_accessor!(sender: &UserId); /// Converts `self` to an `AnyRoomEvent` by adding the given a room ID. pub fn into_full_event(self, room_id: RoomId) -> AnyRoomEvent { match self { Self::Message(ev) => AnyRoomEvent::Message(ev.into_full_event(room_id)), Self::State(ev) => AnyRoomEvent::State(ev.into_full_event(room_id)), Self::RedactedMessage(ev) => AnyRoomEvent::RedactedMessage(ev.into_full_event(room_id)), Self::RedactedState(ev) => AnyRoomEvent::RedactedState(ev.into_full_event(room_id)), } } } #[derive(Deserialize)] #[allow(clippy::exhaustive_structs)] struct EventDeHelper { pub state_key: Option, pub unsigned: Option, } impl<'de> Deserialize<'de> for AnyRoomEvent { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { let json = Box::::deserialize(deserializer)?; let EventDeHelper { state_key, unsigned } = from_raw_json_value(&json)?; if state_key.is_some() { Ok(match unsigned { Some(unsigned) if unsigned.redacted_because.is_some() => { AnyRoomEvent::RedactedState(from_raw_json_value(&json)?) } _ => AnyRoomEvent::State(from_raw_json_value(&json)?), }) } else { Ok(match unsigned { Some(unsigned) if unsigned.redacted_because.is_some() => { AnyRoomEvent::RedactedMessage(from_raw_json_value(&json)?) } _ => AnyRoomEvent::Message(from_raw_json_value(&json)?), }) } } } impl<'de> Deserialize<'de> for AnySyncRoomEvent { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { let json = Box::::deserialize(deserializer)?; let EventDeHelper { state_key, unsigned } = from_raw_json_value(&json)?; if state_key.is_some() { Ok(match unsigned { Some(unsigned) if unsigned.redacted_because.is_some() => { AnySyncRoomEvent::RedactedState(from_raw_json_value(&json)?) } _ => AnySyncRoomEvent::State(from_raw_json_value(&json)?), }) } else { Ok(match unsigned { Some(unsigned) if unsigned.redacted_because.is_some() => { AnySyncRoomEvent::RedactedMessage(from_raw_json_value(&json)?) } _ => AnySyncRoomEvent::Message(from_raw_json_value(&json)?), }) } } } /// Any redacted room event. #[allow(clippy::large_enum_variant, clippy::exhaustive_enums)] #[derive(Clone, Debug, EventEnumFromEvent)] pub enum AnyRedactedRoomEvent { /// Any message event that has been redacted. Message(AnyRedactedMessageEvent), /// Any state event that has been redacted. State(AnyRedactedStateEvent), } impl Redact for AnyRoomEvent { type Redacted = AnyRedactedRoomEvent; /// Redacts `self`, referencing the given event in `unsigned.redacted_because`. /// /// Does nothing for events that are already redacted. fn redact(self, redaction: SyncRoomRedactionEvent, version: &RoomVersionId) -> Self::Redacted { match self { Self::Message(ev) => Self::Redacted::Message(ev.redact(redaction, version)), Self::State(ev) => Self::Redacted::State(ev.redact(redaction, version)), Self::RedactedMessage(ev) => Self::Redacted::Message(ev), Self::RedactedState(ev) => Self::Redacted::State(ev), } } } impl From for AnyRoomEvent { fn from(ev: AnyRedactedRoomEvent) -> Self { match ev { AnyRedactedRoomEvent::Message(ev) => Self::RedactedMessage(ev), AnyRedactedRoomEvent::State(ev) => Self::RedactedState(ev), } } } /// Any redacted sync room event (room event without a `room_id`, as returned in `/sync` responses) #[allow(clippy::large_enum_variant, clippy::exhaustive_enums)] #[derive(Clone, Debug, EventEnumFromEvent)] pub enum AnyRedactedSyncRoomEvent { /// Any sync message event that has been redacted. Message(AnyRedactedSyncMessageEvent), /// Any sync state event that has been redacted. State(AnyRedactedSyncStateEvent), } impl Redact for AnySyncRoomEvent { type Redacted = AnyRedactedSyncRoomEvent; /// Redacts `self`, referencing the given event in `unsigned.redacted_because`. /// /// Does nothing for events that are already redacted. fn redact(self, redaction: SyncRoomRedactionEvent, version: &RoomVersionId) -> Self::Redacted { match self { Self::Message(ev) => Self::Redacted::Message(ev.redact(redaction, version)), Self::State(ev) => Self::Redacted::State(ev.redact(redaction, version)), Self::RedactedMessage(ev) => Self::Redacted::Message(ev), Self::RedactedState(ev) => Self::Redacted::State(ev), } } } impl From for AnySyncRoomEvent { fn from(ev: AnyRedactedSyncRoomEvent) -> Self { match ev { AnyRedactedSyncRoomEvent::Message(ev) => Self::RedactedMessage(ev), AnyRedactedSyncRoomEvent::State(ev) => Self::RedactedState(ev), } } } impl AnyMessageEventContent { /// Get a copy of the event's `m.relates_to` field, if any. /// /// This is a helper function intended for encryption. There should not be a reason to access /// `m.relates_to` without first destructuring an `AnyMessageEventContent` otherwise. pub fn relation(&self) -> Option { #[cfg(feature = "unstable-pre-spec")] use crate::key::verification::{ accept::KeyVerificationAcceptEventContent, cancel::KeyVerificationCancelEventContent, done::KeyVerificationDoneEventContent, key::KeyVerificationKeyEventContent, mac::KeyVerificationMacEventContent, ready::KeyVerificationReadyEventContent, start::KeyVerificationStartEventContent, }; match self { #[cfg(feature = "unstable-pre-spec")] #[rustfmt::skip] Self::KeyVerificationReady(KeyVerificationReadyEventContent { relates_to, .. }) | Self::KeyVerificationStart(KeyVerificationStartEventContent { relates_to, .. }) | Self::KeyVerificationCancel(KeyVerificationCancelEventContent { relates_to, .. }) | Self::KeyVerificationAccept(KeyVerificationAcceptEventContent { relates_to, .. }) | Self::KeyVerificationKey(KeyVerificationKeyEventContent { relates_to, .. }) | Self::KeyVerificationMac(KeyVerificationMacEventContent { relates_to, .. }) | Self::KeyVerificationDone(KeyVerificationDoneEventContent { relates_to, .. }) => { Some(relates_to.clone().into()) }, #[cfg(feature = "unstable-pre-spec")] Self::Reaction(ev) => Some(ev.relates_to.clone().into()), Self::RoomEncrypted(ev) => ev.relates_to.clone(), Self::RoomMessage(ev) => ev.relates_to.clone().map(Into::into), Self::CallAnswer(_) | Self::CallInvite(_) | Self::CallHangup(_) | Self::CallCandidates(_) | Self::RoomMessageFeedback(_) | Self::RoomRedaction(_) | Self::Sticker(_) | Self::_Custom { .. } => None, } } }