diff --git a/crates/ruma-events/Cargo.toml b/crates/ruma-events/Cargo.toml index 6be1c332c7..e7d64ac49d 100644 --- a/crates/ruma-events/Cargo.toml +++ b/crates/ruma-events/Cargo.toml @@ -40,6 +40,7 @@ unstable-msc3927 = ["unstable-msc3551"] unstable-msc3954 = ["unstable-msc1767"] unstable-msc3955 = ["unstable-msc1767"] unstable-msc3956 = ["unstable-msc1767"] +unstable-msc4075 = [] unstable-pdu = [] # Allow some mandatory fields to be missing, defaulting them to an empty string diff --git a/crates/ruma-events/src/call.rs b/crates/ruma-events/src/call.rs index b657ebccc0..9985e4b675 100644 --- a/crates/ruma-events/src/call.rs +++ b/crates/ruma-events/src/call.rs @@ -9,6 +9,8 @@ pub mod invite; #[cfg(feature = "unstable-msc3401")] pub mod member; pub mod negotiate; +#[cfg(feature = "unstable-msc4075")] +pub mod notify; pub mod reject; pub mod select_answer; diff --git a/crates/ruma-events/src/call/notify.rs b/crates/ruma-events/src/call/notify.rs new file mode 100644 index 0000000000..021cb99253 --- /dev/null +++ b/crates/ruma-events/src/call/notify.rs @@ -0,0 +1,77 @@ +//! Types for matrixRTC call notify event ([MSC4075]). +//! +//! This implements the event type defined in MSC4075. +//! +//! [MSC3401]: https://github.com/matrix-org/matrix-spec-proposals/pull/4075 + +use ruma_macros::EventContent; +use serde::{Deserialize, Serialize}; + +use super::member::Application; +use crate::Mentions; + +/// The content of an `m.call.notify` event. +#[derive(Clone, Debug, Deserialize, Serialize, EventContent)] +#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] +#[ruma_event(type = "m.call.notify", kind = MessageLike)] +pub struct CallNotifyEventContent { + /// A unique identifier for the call. + pub call_id: String, + /// The application this notify event applies to. + pub application: ApplicationType, + /// How this notify event should notify the receiver. + pub notify_type: NotifyType, + /// The users that are notified by this event (See [MSC3952](https://github.com/matrix-org/matrix-spec-proposals/pull/3952)(Intentional Mentions)). + #[serde(rename = "m.mentions")] + pub mentions: Mentions, +} + +impl CallNotifyEventContent { + /// Creates a new `CallNotifyEventContent` with the given configuration. + pub fn new( + call_id: String, + application: ApplicationType, + notify_type: NotifyType, + mentions: Mentions, + ) -> Self { + Self { call_id, application, notify_type, mentions } + } +} + +/// How this notify event should notify the receiver. +#[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] +pub enum NotifyType { + /// The receiving client should ring with an audible sound. + #[serde(rename = "ring")] + Ring, + /// The receiving client should display a visual notification. + #[serde(rename = "notify")] + Notify, +} + +/// The type of matrix RTC application. +/// +/// This is different to [`Application`] because application contains all the information from the +/// call.member event. +/// +/// An `Application` can be converted into an `ApplicationType`: +/// ``` +/// let a: Application = myApp; +/// let b: ApplicationType = a.into(); +/// ``` +#[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] +pub enum ApplicationType { + /// A VoIP call. + #[serde(rename = "m.call")] + Call, +} + +impl From for ApplicationType { + fn from(val: Application) -> Self { + match val { + Application::Call(_) => ApplicationType::Call, + } + } +} diff --git a/crates/ruma-events/src/enums.rs b/crates/ruma-events/src/enums.rs index 7c70aeec2e..39233bca8b 100644 --- a/crates/ruma-events/src/enums.rs +++ b/crates/ruma-events/src/enums.rs @@ -93,6 +93,9 @@ event_enum! { #[cfg(feature = "unstable-msc3245")] #[ruma_enum(alias = "m.voice")] "org.matrix.msc3245.voice.v2" => super::voice, + #[cfg(feature = "unstable-msc4075")] + #[ruma_enum(alias = "m.call.notify")] + "org.matrix.msc4075.call.notify" => super::call::notify, } /// Any state event. @@ -361,6 +364,7 @@ impl AnyMessageLikeEventContent { | Self::CallCandidates(_) | Self::RoomRedaction(_) | Self::Sticker(_) + | Self::CallNotify(_) | Self::_Custom { .. } => None, } } diff --git a/crates/ruma-events/tests/it/call.rs b/crates/ruma-events/tests/it/call.rs index af9aae404e..c17de506ac 100644 --- a/crates/ruma-events/tests/it/call.rs +++ b/crates/ruma-events/tests/it/call.rs @@ -1,8 +1,10 @@ +use std::collections::BTreeSet; + use assert_matches2::assert_matches; #[cfg(feature = "unstable-msc2747")] use assign::assign; use js_int::uint; -use ruma_common::{room_id, serde::CanBeEmpty, MilliSecondsSinceUnixEpoch, VoipVersionId}; +use ruma_common::{room_id, serde::CanBeEmpty, MilliSecondsSinceUnixEpoch, UserId, VoipVersionId}; #[cfg(feature = "unstable-msc2747")] use ruma_events::call::CallCapabilities; use ruma_events::{ @@ -12,11 +14,12 @@ use ruma_events::{ hangup::{CallHangupEventContent, Reason}, invite::CallInviteEventContent, negotiate::CallNegotiateEventContent, + notify::{ApplicationType, CallNotifyEventContent, NotifyType}, reject::CallRejectEventContent, select_answer::CallSelectAnswerEventContent, SessionDescription, }, - AnyMessageLikeEvent, AnySyncMessageLikeEvent, MessageLikeEvent, + AnyMessageLikeEvent, AnySyncMessageLikeEvent, Mentions, MessageLikeEvent, }; use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; @@ -598,3 +601,75 @@ fn select_v1_answer_event_deserialization() { assert_eq!(content.selected_party_id, "6336"); assert_eq!(content.version, VoipVersionId::V1); } + +#[test] +fn notify_event_serialization() { + let content_user_mention = CallNotifyEventContent::new( + "abcdef".into(), + ApplicationType::Call, + NotifyType::Ring, + Mentions::with_user_ids(vec![ + <&UserId>::try_from("@user:example.com").unwrap().into(), + <&UserId>::try_from("@user2:example.com").unwrap().into(), + ]), + ); + + let content_room_mention = CallNotifyEventContent::new( + "abcdef".into(), + ApplicationType::Call, + NotifyType::Ring, + Mentions::with_room_mention(), + ); + + assert_eq!( + to_json_value(&content_user_mention).unwrap(), + json!({ + "call_id": "abcdef", + "application": "m.call", + "m.mentions": {"user_ids": ["@user2:example.com","@user:example.com"]}, + "notify_type": "ring", + }) + ); + assert_eq!( + to_json_value(&content_room_mention).unwrap(), + json!({ + "call_id": "abcdef", + "application": "m.call", + "m.mentions": {"room": true}, + "notify_type": "ring", + }) + ); +} + +#[test] +fn notify_event_deserialization() { + let json_data = json!({ + "content": { + "call_id": "abcdef", + "application": "m.call", + "m.mentions": {"room": false, "user_ids": ["@user:example.com", "@user2:example.com"]}, + "notify_type": "ring", + }, + "event_id": "$event:notareal.hs", + "origin_server_ts": 134_829_848, + "room_id": "!roomid:notareal.hs", + "sender": "@user:notareal.hs", + "type": "m.call.notify", + }); + + let event = from_json_value::(json_data).unwrap(); + assert_matches!( + event, + AnyMessageLikeEvent::CallNotify(MessageLikeEvent::Original(message_event)) + ); + let content = message_event.content; + assert_eq!(content.call_id, "abcdef"); + assert!(!content.mentions.room); + assert_eq!( + content.mentions.user_ids, + BTreeSet::from([ + <&UserId>::try_from("@user:example.com").unwrap().to_owned(), + <&UserId>::try_from("@user2:example.com").unwrap().to_owned(), + ]) + ); +} diff --git a/crates/ruma/Cargo.toml b/crates/ruma/Cargo.toml index d08ff9e60d..178f65df83 100644 --- a/crates/ruma/Cargo.toml +++ b/crates/ruma/Cargo.toml @@ -214,6 +214,7 @@ unstable-msc3955 = ["ruma-events?/unstable-msc3955"] unstable-msc3956 = ["ruma-events?/unstable-msc3956"] unstable-msc3958 = ["ruma-common/unstable-msc3958"] unstable-msc3983 = ["ruma-client-api?/unstable-msc3983"] +unstable-msc4075 = ["ruma-events?/unstable-msc4075"] unstable-pdu = ["ruma-events?/unstable-pdu"] unstable-unspecified = [ "ruma-common/unstable-unspecified", @@ -259,6 +260,7 @@ __ci = [ "unstable-msc3956", "unstable-msc3958", "unstable-msc3983", + "unstable-msc4075", ] [dependencies]