events: Add support for extensible notice and emote events

As defined in MSC1767
This commit is contained in:
Kévin Commaille 2022-03-02 11:27:34 +01:00 committed by Jonas Platte
parent f78d3480a5
commit 99cd70b430
6 changed files with 254 additions and 1 deletions

View File

@ -6,7 +6,7 @@ Breaking changes:
Improvements:
* Add unstable support for extensible text message events ([MSC1767](https://github.com/matrix-org/matrix-spec-proposals/pull/1767))
* Add unstable support for extensible text, notice and emote message events ([MSC1767](https://github.com/matrix-org/matrix-spec-proposals/pull/1767))
# 0.26.0

View File

@ -0,0 +1,54 @@
//! Types for extensible emote message events ([MSC1767]).
//!
//! [MSC1767]: https://github.com/matrix-org/matrix-spec-proposals/pull/1767
use ruma_macros::EventContent;
use serde::{Deserialize, Serialize};
use crate::{
message::{MessageContent, TextMessage},
room::message::Relation,
};
/// The payload for an extensible emote message.
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[ruma_event(type = "m.emote", kind = MessageLike)]
pub struct EmoteEventContent {
/// The message's text content.
#[serde(flatten)]
pub message: MessageContent,
/// Information about related messages for [rich replies].
///
/// [rich replies]: https://spec.matrix.org/v1.2/client-server-api/#rich-replies
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub relates_to: Option<Relation>,
}
impl EmoteEventContent {
/// A convenience constructor to create a plain text message.
pub fn plain(body: impl Into<String>) -> Self {
Self { message: MessageContent::plain(body), relates_to: None }
}
/// A convenience constructor to create an HTML message.
pub fn html(body: impl Into<String>, html_body: impl Into<String>) -> Self {
Self { message: MessageContent::html(body, html_body), relates_to: None }
}
/// A convenience constructor to create a Markdown message.
///
/// Returns an HTML message if some Markdown formatting was detected, otherwise returns a plain
/// text message.
#[cfg(feature = "markdown")]
pub fn markdown(body: impl AsRef<str> + Into<String>) -> Self {
Self { message: MessageContent::markdown(body), relates_to: None }
}
}
impl TextMessage for EmoteEventContent {
fn message(&self) -> &MessageContent {
&self.message
}
}

View File

@ -37,6 +37,8 @@ event_enum! {
"m.call.invite",
"m.call.hangup",
"m.call.candidates",
#[cfg(feature = "unstable-msc1767")]
"m.emote",
"m.key.verification.ready",
"m.key.verification.start",
"m.key.verification.cancel",
@ -46,6 +48,8 @@ event_enum! {
"m.key.verification.done",
#[cfg(feature = "unstable-msc1767")]
"m.message",
#[cfg(feature = "unstable-msc1767")]
"m.notice",
#[cfg(feature = "unstable-msc2677")]
"m.reaction",
"m.room.encrypted",
@ -356,6 +360,10 @@ impl AnyMessageLikeEventContent {
Self::RoomMessage(ev) => ev.relates_to.clone().map(Into::into),
#[cfg(feature = "unstable-msc1767")]
Self::Message(ev) => ev.relates_to.clone().map(Into::into),
#[cfg(feature = "unstable-msc1767")]
Self::Notice(ev) => ev.relates_to.clone().map(Into::into),
#[cfg(feature = "unstable-msc1767")]
Self::Emote(ev) => ev.relates_to.clone().map(Into::into),
Self::CallAnswer(_)
| Self::CallInvite(_)
| Self::CallHangup(_)

View File

@ -172,12 +172,16 @@ pub mod macros {
pub mod call;
pub mod direct;
pub mod dummy;
#[cfg(feature = "unstable-msc1767")]
pub mod emote;
pub mod forwarded_room_key;
pub mod fully_read;
pub mod ignored_user_list;
pub mod key;
#[cfg(feature = "unstable-msc1767")]
pub mod message;
#[cfg(feature = "unstable-msc1767")]
pub mod notice;
#[cfg(feature = "unstable-pdu")]
pub mod pdu;
pub mod policy;

View File

@ -0,0 +1,54 @@
//! Types for extensible notice message events ([MSC1767]).
//!
//! [MSC1767]: https://github.com/matrix-org/matrix-spec-proposals/pull/1767
use ruma_macros::EventContent;
use serde::{Deserialize, Serialize};
use crate::{
message::{MessageContent, TextMessage},
room::message::Relation,
};
/// The payload for an extensible notice message.
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[ruma_event(type = "m.notice", kind = MessageLike)]
pub struct NoticeEventContent {
/// The message's text content.
#[serde(flatten)]
pub message: MessageContent,
/// Information about related messages for [rich replies].
///
/// [rich replies]: https://spec.matrix.org/v1.2/client-server-api/#rich-replies
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub relates_to: Option<Relation>,
}
impl NoticeEventContent {
/// A convenience constructor to create a plain text message.
pub fn plain(body: impl Into<String>) -> Self {
Self { message: MessageContent::plain(body), relates_to: None }
}
/// A convenience constructor to create an HTML message.
pub fn html(body: impl Into<String>, html_body: impl Into<String>) -> Self {
Self { message: MessageContent::html(body, html_body), relates_to: None }
}
/// A convenience constructor to create a Markdown message.
///
/// Returns an HTML message if some Markdown formatting was detected, otherwise returns a plain
/// text message.
#[cfg(feature = "markdown")]
pub fn markdown(body: impl AsRef<str> + Into<String>) -> Self {
Self { message: MessageContent::markdown(body), relates_to: None }
}
}
impl TextMessage for NoticeEventContent {
fn message(&self) -> &MessageContent {
&self.message
}
}

View File

@ -5,7 +5,9 @@ use js_int::uint;
use matches::assert_matches;
use ruma_common::MilliSecondsSinceUnixEpoch;
use ruma_events::{
emote::EmoteEventContent,
message::MessageEventContent,
notice::NoticeEventContent,
room::message::{InReplyTo, Relation},
AnyMessageLikeEvent, MessageLikeEvent, Unsigned,
};
@ -219,3 +221,134 @@ fn message_event_deserialization() {
&& unsigned.is_empty()
);
}
#[test]
fn notice_event_serialization() {
let event = MessageLikeEvent {
content: NoticeEventContent::plain("Hello, I'm a robot!"),
event_id: event_id!("$event:notareal.hs").to_owned(),
sender: user_id!("@user:notareal.hs").to_owned(),
origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(134_829_848)),
room_id: room_id!("!roomid:notareal.hs").to_owned(),
unsigned: Unsigned::default(),
};
assert_eq!(
to_json_value(&event).unwrap(),
json!({
"content": {
"org.matrix.msc1767.text": "Hello, I'm a robot!",
},
"event_id": "$event:notareal.hs",
"origin_server_ts": 134_829_848,
"room_id": "!roomid:notareal.hs",
"sender": "@user:notareal.hs",
"type": "m.notice",
})
);
}
#[test]
fn notice_event_deserialization() {
let json_data = json!({
"content": {
"org.matrix.msc1767.message": [
{ "body": "Hello, I'm a <em>robot</em>!", "mimetype": "text/html"},
{ "body": "Hello, I'm a robot!" },
]
},
"event_id": "$event:notareal.hs",
"origin_server_ts": 134_829_848,
"room_id": "!roomid:notareal.hs",
"sender": "@user:notareal.hs",
"type": "m.notice",
});
assert_matches!(
from_json_value::<AnyMessageLikeEvent>(json_data).unwrap(),
AnyMessageLikeEvent::Notice(MessageLikeEvent {
content: NoticeEventContent {
message,
..
},
event_id,
origin_server_ts,
room_id,
sender,
unsigned
}) if event_id == event_id!("$event:notareal.hs")
&& message.find_plain().unwrap() == "Hello, I'm a robot!"
&& message.find_html().unwrap() == "Hello, I'm a <em>robot</em>!"
&& origin_server_ts == MilliSecondsSinceUnixEpoch(uint!(134_829_848))
&& room_id == room_id!("!roomid:notareal.hs")
&& sender == user_id!("@user:notareal.hs")
&& unsigned.is_empty()
);
}
#[test]
fn emote_event_serialization() {
let event = MessageLikeEvent {
content: EmoteEventContent::html(
"is testing some code…",
"is testing some <code>code</code>…",
),
event_id: event_id!("$event:notareal.hs").to_owned(),
sender: user_id!("@user:notareal.hs").to_owned(),
origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(134_829_848)),
room_id: room_id!("!roomid:notareal.hs").to_owned(),
unsigned: Unsigned::default(),
};
assert_eq!(
to_json_value(&event).unwrap(),
json!({
"content": {
"org.matrix.msc1767.message": [
{ "body": "is testing some <code>code</code>…", "mimetype": "text/html" },
{ "body": "is testing some code…", "mimetype": "text/plain" },
]
},
"event_id": "$event:notareal.hs",
"origin_server_ts": 134_829_848,
"room_id": "!roomid:notareal.hs",
"sender": "@user:notareal.hs",
"type": "m.emote",
})
);
}
#[test]
fn emote_event_deserialization() {
let json_data = json!({
"content": {
"org.matrix.msc1767.text": "is testing some code…",
},
"event_id": "$event:notareal.hs",
"origin_server_ts": 134_829_848,
"room_id": "!roomid:notareal.hs",
"sender": "@user:notareal.hs",
"type": "m.emote",
});
assert_matches!(
from_json_value::<AnyMessageLikeEvent>(json_data).unwrap(),
AnyMessageLikeEvent::Emote(MessageLikeEvent {
content: EmoteEventContent {
message,
..
},
event_id,
origin_server_ts,
room_id,
sender,
unsigned
}) if event_id == event_id!("$event:notareal.hs")
&& message.find_plain().unwrap() == "is testing some code…"
&& message.find_html().is_none()
&& origin_server_ts == MilliSecondsSinceUnixEpoch(uint!(134_829_848))
&& room_id == room_id!("!roomid:notareal.hs")
&& sender == user_id!("@user:notareal.hs")
&& unsigned.is_empty()
);
}