events: Add support for transitional extensible voice messages
According to MSC3245
This commit is contained in:
parent
f3abeed5c1
commit
c6d11c78a7
@ -75,7 +75,18 @@ impl AudioEventContent {
|
|||||||
content: AudioMessageEventContent,
|
content: AudioMessageEventContent,
|
||||||
relates_to: Option<Relation>,
|
relates_to: Option<Relation>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let AudioMessageEventContent { body, source, info, message, file, audio } = content;
|
// Otherwise the `voice` field has an extra indentation
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let AudioMessageEventContent {
|
||||||
|
body,
|
||||||
|
source,
|
||||||
|
info,
|
||||||
|
message,
|
||||||
|
file,
|
||||||
|
audio,
|
||||||
|
#[cfg(feature = "unstable-msc3245")]
|
||||||
|
voice: _,
|
||||||
|
} = content;
|
||||||
|
|
||||||
let message = message.unwrap_or_else(|| MessageContent::plain(body));
|
let message = message.unwrap_or_else(|| MessageContent::plain(body));
|
||||||
let file = file.unwrap_or_else(|| {
|
let file = file.unwrap_or_else(|| {
|
||||||
|
@ -18,6 +18,8 @@ use crate::events::file::{FileContent, FileContentInfo, FileEventContent};
|
|||||||
use crate::events::image::{ImageContent, ImageEventContent, ThumbnailContent};
|
use crate::events::image::{ImageContent, ImageEventContent, ThumbnailContent};
|
||||||
#[cfg(feature = "unstable-msc3553")]
|
#[cfg(feature = "unstable-msc3553")]
|
||||||
use crate::events::video::{VideoContent, VideoEventContent};
|
use crate::events::video::{VideoContent, VideoEventContent};
|
||||||
|
#[cfg(feature = "unstable-msc3245")]
|
||||||
|
use crate::events::voice::{VoiceContent, VoiceEventContent};
|
||||||
#[cfg(feature = "unstable-msc1767")]
|
#[cfg(feature = "unstable-msc1767")]
|
||||||
use crate::events::{
|
use crate::events::{
|
||||||
emote::EmoteEventContent,
|
emote::EmoteEventContent,
|
||||||
@ -279,6 +281,20 @@ impl From<VideoEventContent> for RoomMessageEventContent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "unstable-msc3245")]
|
||||||
|
impl From<VoiceEventContent> for RoomMessageEventContent {
|
||||||
|
fn from(content: VoiceEventContent) -> Self {
|
||||||
|
let VoiceEventContent { message, file, audio, voice, relates_to } = content;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
msgtype: MessageType::Audio(AudioMessageEventContent::from_extensible_voice_content(
|
||||||
|
message, file, audio, voice,
|
||||||
|
)),
|
||||||
|
relates_to,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The content that is specific to each message type variant.
|
/// The content that is specific to each message type variant.
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
@ -570,6 +586,13 @@ pub struct AudioMessageEventContent {
|
|||||||
#[cfg(feature = "unstable-msc3246")]
|
#[cfg(feature = "unstable-msc3246")]
|
||||||
#[serde(rename = "org.matrix.msc1767.audio", skip_serializing_if = "Option::is_none")]
|
#[serde(rename = "org.matrix.msc1767.audio", skip_serializing_if = "Option::is_none")]
|
||||||
pub audio: Option<AudioContent>,
|
pub audio: Option<AudioContent>,
|
||||||
|
|
||||||
|
/// Extensible-event voice flag of the message.
|
||||||
|
///
|
||||||
|
/// If present, this should be represented as a voice message.
|
||||||
|
#[cfg(feature = "unstable-msc3245")]
|
||||||
|
#[serde(rename = "org.matrix.msc3245.voice", skip_serializing_if = "Option::is_none")]
|
||||||
|
pub voice: Option<VoiceContent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioMessageEventContent {
|
impl AudioMessageEventContent {
|
||||||
@ -586,6 +609,8 @@ impl AudioMessageEventContent {
|
|||||||
)),
|
)),
|
||||||
#[cfg(feature = "unstable-msc3246")]
|
#[cfg(feature = "unstable-msc3246")]
|
||||||
audio: Some(info.as_deref().map_or_else(AudioContent::default, Into::into)),
|
audio: Some(info.as_deref().map_or_else(AudioContent::default, Into::into)),
|
||||||
|
#[cfg(feature = "unstable-msc3245")]
|
||||||
|
voice: None,
|
||||||
body,
|
body,
|
||||||
source: MediaSource::Plain(url),
|
source: MediaSource::Plain(url),
|
||||||
info,
|
info,
|
||||||
@ -602,6 +627,8 @@ impl AudioMessageEventContent {
|
|||||||
file: Some(FileContent::encrypted(file.url.clone(), (&file).into(), None)),
|
file: Some(FileContent::encrypted(file.url.clone(), (&file).into(), None)),
|
||||||
#[cfg(feature = "unstable-msc3246")]
|
#[cfg(feature = "unstable-msc3246")]
|
||||||
audio: Some(AudioContent::default()),
|
audio: Some(AudioContent::default()),
|
||||||
|
#[cfg(feature = "unstable-msc3245")]
|
||||||
|
voice: None,
|
||||||
body,
|
body,
|
||||||
source: MediaSource::Encrypted(Box::new(file)),
|
source: MediaSource::Encrypted(Box::new(file)),
|
||||||
info: None,
|
info: None,
|
||||||
@ -623,7 +650,30 @@ impl AudioMessageEventContent {
|
|||||||
let source = (&file).into();
|
let source = (&file).into();
|
||||||
let info = AudioInfo::from_extensible_content(file.info.as_deref(), &audio).map(Box::new);
|
let info = AudioInfo::from_extensible_content(file.info.as_deref(), &audio).map(Box::new);
|
||||||
|
|
||||||
Self { message: Some(message), file: Some(file), audio: Some(audio), body, source, info }
|
Self {
|
||||||
|
message: Some(message),
|
||||||
|
file: Some(file),
|
||||||
|
audio: Some(audio),
|
||||||
|
#[cfg(feature = "unstable-msc3245")]
|
||||||
|
voice: None,
|
||||||
|
body,
|
||||||
|
source,
|
||||||
|
info,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `AudioMessageEventContent` with the given message, file info, audio info and
|
||||||
|
/// voice flag.
|
||||||
|
#[cfg(feature = "unstable-msc3245")]
|
||||||
|
pub fn from_extensible_voice_content(
|
||||||
|
message: MessageContent,
|
||||||
|
ext_file: FileContent,
|
||||||
|
audio: AudioContent,
|
||||||
|
voice: VoiceContent,
|
||||||
|
) -> Self {
|
||||||
|
let mut content = Self::from_extensible_content(message, ext_file, audio);
|
||||||
|
content.voice = Some(voice);
|
||||||
|
content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
use serde::{de, Deserialize};
|
use serde::{de, Deserialize};
|
||||||
use serde_json::value::RawValue as RawJsonValue;
|
use serde_json::value::RawValue as RawJsonValue;
|
||||||
|
|
||||||
|
#[cfg(feature = "unstable-msc3245")]
|
||||||
|
use super::VoiceContent;
|
||||||
#[cfg(feature = "unstable-msc3246")]
|
#[cfg(feature = "unstable-msc3246")]
|
||||||
use super::{AudioContent, AudioInfo, AudioMessageEventContent};
|
use super::{AudioContent, AudioInfo, AudioMessageEventContent};
|
||||||
#[cfg(feature = "unstable-msc3551")]
|
#[cfg(feature = "unstable-msc3551")]
|
||||||
@ -95,6 +97,16 @@ pub struct AudioMessageEventContentDeHelper {
|
|||||||
/// Extensible-event audio info of the message, with unstable name.
|
/// Extensible-event audio info of the message, with unstable name.
|
||||||
#[serde(rename = "org.matrix.msc1767.audio")]
|
#[serde(rename = "org.matrix.msc1767.audio")]
|
||||||
pub audio_unstable: Option<AudioContent>,
|
pub audio_unstable: Option<AudioContent>,
|
||||||
|
|
||||||
|
/// Extensible-event voice flag of the message, with stable name.
|
||||||
|
#[cfg(feature = "unstable-msc3245")]
|
||||||
|
#[serde(rename = "m.voice")]
|
||||||
|
pub voice_stable: Option<VoiceContent>,
|
||||||
|
|
||||||
|
/// Extensible-event voice flag of the message, with unstable name.
|
||||||
|
#[cfg(feature = "unstable-msc3245")]
|
||||||
|
#[serde(rename = "org.matrix.msc3245.voice")]
|
||||||
|
pub voice_unstable: Option<VoiceContent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "unstable-msc3246")]
|
#[cfg(feature = "unstable-msc3246")]
|
||||||
@ -109,12 +121,27 @@ impl From<AudioMessageEventContentDeHelper> for AudioMessageEventContent {
|
|||||||
file_unstable,
|
file_unstable,
|
||||||
audio_stable,
|
audio_stable,
|
||||||
audio_unstable,
|
audio_unstable,
|
||||||
|
#[cfg(feature = "unstable-msc3245")]
|
||||||
|
voice_stable,
|
||||||
|
#[cfg(feature = "unstable-msc3245")]
|
||||||
|
voice_unstable,
|
||||||
} = helper;
|
} = helper;
|
||||||
|
|
||||||
let file = file_stable.or(file_unstable);
|
let file = file_stable.or(file_unstable);
|
||||||
let audio = audio_stable.or(audio_unstable);
|
let audio = audio_stable.or(audio_unstable);
|
||||||
|
#[cfg(feature = "unstable-msc3245")]
|
||||||
|
let voice = voice_stable.or(voice_unstable);
|
||||||
|
|
||||||
Self { body, source, info, message, file, audio }
|
Self {
|
||||||
|
body,
|
||||||
|
source,
|
||||||
|
info,
|
||||||
|
message,
|
||||||
|
file,
|
||||||
|
audio,
|
||||||
|
#[cfg(feature = "unstable-msc3245")]
|
||||||
|
voice,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,10 +6,25 @@ use ruma_macros::EventContent;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
audio::AudioContent, file::FileContent, message::MessageContent, room::message::Relation,
|
audio::AudioContent,
|
||||||
|
file::FileContent,
|
||||||
|
message::{MessageContent, TryFromExtensibleError},
|
||||||
|
room::message::{AudioMessageEventContent, Relation},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The payload for an extensible audio message.
|
/// The payload for an extensible voice message.
|
||||||
|
///
|
||||||
|
/// This is the new primary type introduced in [MSC3245] and should not be sent before the end of
|
||||||
|
/// the transition period. See the documentation of the [`message`] module for more information.
|
||||||
|
///
|
||||||
|
/// `VoiceEventContent` can be converted to a [`RoomMessageEventContent`] with a
|
||||||
|
/// [`MessageType::Audio`] with the `m.voice` flag. You can convert it back with
|
||||||
|
/// [`VoiceEventContent::try_from_audio_room_message()`].
|
||||||
|
///
|
||||||
|
/// [MSC3245]: https://github.com/matrix-org/matrix-spec-proposals/pull/3245
|
||||||
|
/// [`message`]: super::message
|
||||||
|
/// [`RoomMessageEventContent`]: super::room::message::RoomMessageEventContent
|
||||||
|
/// [`MessageType::Audio`]: super::room::message::MessageType::Audio
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
|
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
|
||||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
#[ruma_event(type = "m.voice", kind = MessageLike)]
|
#[ruma_event(type = "m.voice", kind = MessageLike)]
|
||||||
@ -57,6 +72,30 @@ impl VoiceEventContent {
|
|||||||
relates_to: None,
|
relates_to: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new `VoiceEventContent` from the given `AudioMessageEventContent` and optional
|
||||||
|
/// relation.
|
||||||
|
///
|
||||||
|
/// This can fail if the `AudioMessageEventContent` is not a voice message.
|
||||||
|
pub fn try_from_audio_room_message(
|
||||||
|
content: AudioMessageEventContent,
|
||||||
|
relates_to: Option<Relation>,
|
||||||
|
) -> Result<Self, TryFromExtensibleError> {
|
||||||
|
let AudioMessageEventContent { body, source, info, message, file, audio, voice } = content;
|
||||||
|
|
||||||
|
let message = message.unwrap_or_else(|| MessageContent::plain(body));
|
||||||
|
let file = file.unwrap_or_else(|| {
|
||||||
|
FileContent::from_room_message_content(source, info.as_deref(), None)
|
||||||
|
});
|
||||||
|
let audio = audio.or_else(|| info.as_deref().map(Into::into)).unwrap_or_default();
|
||||||
|
let voice = if let Some(voice) = voice {
|
||||||
|
voice
|
||||||
|
} else {
|
||||||
|
return Err(TryFromExtensibleError::MissingField("m.voice".to_owned()));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self { message, file, audio, voice, relates_to })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Voice content.
|
/// Voice content.
|
||||||
|
@ -10,8 +10,13 @@ use ruma_common::{
|
|||||||
events::{
|
events::{
|
||||||
audio::AudioContent,
|
audio::AudioContent,
|
||||||
file::{FileContent, FileContentInfo},
|
file::{FileContent, FileContentInfo},
|
||||||
room::message::{InReplyTo, Relation},
|
room::{
|
||||||
voice::VoiceEventContent,
|
message::{
|
||||||
|
AudioMessageEventContent, InReplyTo, MessageType, Relation, RoomMessageEventContent,
|
||||||
|
},
|
||||||
|
MediaSource,
|
||||||
|
},
|
||||||
|
voice::{VoiceContent, VoiceEventContent},
|
||||||
AnyMessageLikeEvent, MessageLikeEvent, MessageLikeUnsigned,
|
AnyMessageLikeEvent, MessageLikeEvent, MessageLikeUnsigned,
|
||||||
},
|
},
|
||||||
mxc_uri, room_id, user_id, MilliSecondsSinceUnixEpoch,
|
mxc_uri, room_id, user_id, MilliSecondsSinceUnixEpoch,
|
||||||
@ -89,7 +94,7 @@ fn event_serialization() {
|
|||||||
fn message_event_deserialization() {
|
fn message_event_deserialization() {
|
||||||
let json_data = json!({
|
let json_data = json!({
|
||||||
"content": {
|
"content": {
|
||||||
"org.matrix.msc1767.text": "Voice message",
|
"m.text": "Voice message",
|
||||||
"m.file": {
|
"m.file": {
|
||||||
"url": "mxc://notareal.hs/abcdef",
|
"url": "mxc://notareal.hs/abcdef",
|
||||||
"name": "voice_message.ogg",
|
"name": "voice_message.ogg",
|
||||||
@ -141,3 +146,95 @@ fn message_event_deserialization() {
|
|||||||
&& unsigned.is_empty()
|
&& unsigned.is_empty()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn room_message_serialization() {
|
||||||
|
let message_event_content = RoomMessageEventContent::new(MessageType::Audio(assign!(
|
||||||
|
AudioMessageEventContent::plain(
|
||||||
|
"Upload: voice_message.ogg".to_owned(),
|
||||||
|
mxc_uri!("mxc://notareal.hs/file").to_owned(),
|
||||||
|
None,
|
||||||
|
), {
|
||||||
|
voice: Some(VoiceContent::new()),
|
||||||
|
}
|
||||||
|
)));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
to_json_value(&message_event_content).unwrap(),
|
||||||
|
json!({
|
||||||
|
"body": "Upload: voice_message.ogg",
|
||||||
|
"url": "mxc://notareal.hs/file",
|
||||||
|
"msgtype": "m.audio",
|
||||||
|
"org.matrix.msc1767.text": "Upload: voice_message.ogg",
|
||||||
|
"org.matrix.msc1767.file": {
|
||||||
|
"url": "mxc://notareal.hs/file",
|
||||||
|
},
|
||||||
|
"org.matrix.msc1767.audio": {},
|
||||||
|
"org.matrix.msc3245.voice": {},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn room_message_stable_deserialization() {
|
||||||
|
let json_data = json!({
|
||||||
|
"body": "Upload: voice_message.ogg",
|
||||||
|
"url": "mxc://notareal.hs/file",
|
||||||
|
"msgtype": "m.audio",
|
||||||
|
"m.text": "Upload: voice_message.ogg",
|
||||||
|
"m.file": {
|
||||||
|
"url": "mxc://notareal.hs/file",
|
||||||
|
},
|
||||||
|
"m.audio": {},
|
||||||
|
"m.voice": {},
|
||||||
|
});
|
||||||
|
|
||||||
|
let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
|
||||||
|
assert_matches!(event_content.msgtype, MessageType::Audio(_));
|
||||||
|
if let MessageType::Audio(content) = event_content.msgtype {
|
||||||
|
assert_eq!(content.body, "Upload: voice_message.ogg");
|
||||||
|
assert_matches!(content.source, MediaSource::Plain(_));
|
||||||
|
if let MediaSource::Plain(url) = content.source {
|
||||||
|
assert_eq!(url, "mxc://notareal.hs/file");
|
||||||
|
}
|
||||||
|
let message = content.message.unwrap();
|
||||||
|
assert_eq!(message.len(), 1);
|
||||||
|
assert_eq!(message[0].body, "Upload: voice_message.ogg");
|
||||||
|
let file = content.file.unwrap();
|
||||||
|
assert_eq!(file.url, "mxc://notareal.hs/file");
|
||||||
|
assert!(!file.is_encrypted());
|
||||||
|
assert!(content.voice.is_some());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn room_message_unstable_deserialization() {
|
||||||
|
let json_data = json!({
|
||||||
|
"body": "Upload: voice_message.ogg",
|
||||||
|
"url": "mxc://notareal.hs/file",
|
||||||
|
"msgtype": "m.audio",
|
||||||
|
"org.matrix.msc1767.text": "Upload: voice_message.ogg",
|
||||||
|
"org.matrix.msc1767.file": {
|
||||||
|
"url": "mxc://notareal.hs/file",
|
||||||
|
},
|
||||||
|
"org.matrix.msc1767.audio": {},
|
||||||
|
"org.matrix.msc3245.voice": {},
|
||||||
|
});
|
||||||
|
|
||||||
|
let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
|
||||||
|
assert_matches!(event_content.msgtype, MessageType::Audio(_));
|
||||||
|
if let MessageType::Audio(content) = event_content.msgtype {
|
||||||
|
assert_eq!(content.body, "Upload: voice_message.ogg");
|
||||||
|
assert_matches!(content.source, MediaSource::Plain(_));
|
||||||
|
if let MediaSource::Plain(url) = content.source {
|
||||||
|
assert_eq!(url, "mxc://notareal.hs/file");
|
||||||
|
}
|
||||||
|
let message = content.message.unwrap();
|
||||||
|
assert_eq!(message.len(), 1);
|
||||||
|
assert_eq!(message[0].body, "Upload: voice_message.ogg");
|
||||||
|
let file = content.file.unwrap();
|
||||||
|
assert_eq!(file.url, "mxc://notareal.hs/file");
|
||||||
|
assert!(!file.is_encrypted());
|
||||||
|
assert!(content.voice.is_some());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user