events: Add support for transitional extensible image messages
According to MSC3552
This commit is contained in:
		
							parent
							
								
									826c379e80
								
							
						
					
					
						commit
						e94a8db7f4
					
				| @ -12,7 +12,7 @@ use super::{ | ||||
|     message::MessageContent, | ||||
|     room::{ | ||||
|         message::{FileInfo, FileMessageEventContent, Relation}, | ||||
|         EncryptedFile, JsonWebKey, MediaSource, | ||||
|         EncryptedFile, ImageInfo, JsonWebKey, MediaSource, | ||||
|     }, | ||||
| }; | ||||
| use crate::{serde::Base64, MxcUri}; | ||||
| @ -218,6 +218,13 @@ impl From<&FileInfo> for FileContentInfo { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<&ImageInfo> for FileContentInfo { | ||||
|     fn from(info: &ImageInfo) -> Self { | ||||
|         let ImageInfo { mimetype, size, .. } = info; | ||||
|         Self { mimetype: mimetype.to_owned(), size: size.to_owned(), ..Default::default() } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// The encryption info of a file sent to a room with end-to-end encryption enabled.
 | ||||
| ///
 | ||||
| /// To create an instance of this type, first create a `EncryptedContentInit` and convert it via
 | ||||
|  | ||||
| @ -9,11 +9,26 @@ use serde::{Deserialize, Serialize}; | ||||
| use super::{ | ||||
|     file::{EncryptedContent, FileContent}, | ||||
|     message::MessageContent, | ||||
|     room::message::Relation, | ||||
|     room::{ | ||||
|         message::{ImageMessageEventContent, Relation}, | ||||
|         ImageInfo, MediaSource, ThumbnailInfo, | ||||
|     }, | ||||
| }; | ||||
| use crate::MxcUri; | ||||
| 
 | ||||
| /// The payload for an extensible image message.
 | ||||
| ///
 | ||||
| /// This is the new primary type introduced in [MSC3552] and should not be sent before the end of
 | ||||
| /// the transition period. See the documentation of the [`message`] module for more information.
 | ||||
| ///
 | ||||
| /// `ImageEventContent` can be converted to a [`RoomMessageEventContent`] with a
 | ||||
| /// [`MessageType::Image`]. You can convert it back with
 | ||||
| /// [`ImageEventContent::from_image_room_message()`].
 | ||||
| ///
 | ||||
| /// [MSC3552]: https://github.com/matrix-org/matrix-spec-proposals/pull/3552
 | ||||
| /// [`message`]: super::message
 | ||||
| /// [`RoomMessageEventContent`]: super::room::message::RoomMessageEventContent
 | ||||
| /// [`MessageType::Image`]: super::room::message::MessageType::Image
 | ||||
| #[derive(Clone, Debug, Serialize, Deserialize, EventContent)] | ||||
| #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] | ||||
| #[ruma_event(type = "m.image", kind = MessageLike)] | ||||
| @ -74,6 +89,45 @@ impl ImageEventContent { | ||||
|             relates_to: None, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Create a new `ImageEventContent` from the given `ImageMessageEventContent` and optional
 | ||||
|     /// relation.
 | ||||
|     pub fn from_image_room_message( | ||||
|         content: ImageMessageEventContent, | ||||
|         relates_to: Option<Relation>, | ||||
|     ) -> Self { | ||||
|         let ImageMessageEventContent { | ||||
|             body, | ||||
|             source, | ||||
|             info, | ||||
|             message, | ||||
|             file, | ||||
|             image, | ||||
|             thumbnail, | ||||
|             caption, | ||||
|         } = 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 image = | ||||
|             image.or_else(|| info.as_deref().map(|info| Box::new(info.into()))).unwrap_or_default(); | ||||
|         let thumbnail = thumbnail | ||||
|             .or_else(|| { | ||||
|                 info.as_deref() | ||||
|                     .and_then(|info| { | ||||
|                         ThumbnailContent::from_room_message_content( | ||||
|                             info.thumbnail_source.as_ref(), | ||||
|                             info.thumbnail_info.as_deref(), | ||||
|                         ) | ||||
|                     }) | ||||
|                     .map(|thumbnail| vec![thumbnail]) | ||||
|             }) | ||||
|             .unwrap_or_default(); | ||||
| 
 | ||||
|         Self { message, file, image, thumbnail, caption, relates_to } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Image content.
 | ||||
| @ -99,6 +153,25 @@ impl ImageContent { | ||||
|     pub fn with_size(width: UInt, height: UInt) -> Self { | ||||
|         Self { height: Some(height), width: Some(width) } | ||||
|     } | ||||
| 
 | ||||
|     /// Whether this `ImageContent` is empty.
 | ||||
|     pub fn is_empty(&self) -> bool { | ||||
|         self.height.is_none() && self.width.is_none() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<&ImageInfo> for ImageContent { | ||||
|     fn from(info: &ImageInfo) -> Self { | ||||
|         let ImageInfo { height, width, .. } = info; | ||||
|         Self { height: height.to_owned(), width: width.to_owned() } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<&ThumbnailInfo> for ImageContent { | ||||
|     fn from(info: &ThumbnailInfo) -> Self { | ||||
|         let ThumbnailInfo { height, width, .. } = info; | ||||
|         Self { height: height.to_owned(), width: width.to_owned() } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Thumbnail content.
 | ||||
| @ -119,6 +192,22 @@ impl ThumbnailContent { | ||||
|     pub fn new(file: ThumbnailFileContent, image: Option<Box<ImageContent>>) -> Self { | ||||
|         Self { file, image } | ||||
|     } | ||||
| 
 | ||||
|     /// Create a `ThumbnailContent` with the given thumbnail source and info.
 | ||||
|     ///
 | ||||
|     /// Returns `None` if no thumbnail was found.
 | ||||
|     pub fn from_room_message_content( | ||||
|         thumbnail_source: Option<&MediaSource>, | ||||
|         thumbnail_info: Option<&ThumbnailInfo>, | ||||
|     ) -> Option<Self> { | ||||
|         thumbnail_source.map(|thumbnail_source| { | ||||
|             let file = | ||||
|                 ThumbnailFileContent::from_room_message_content(thumbnail_source, thumbnail_info); | ||||
|             let image = thumbnail_info.map(|info| Box::new(info.into())); | ||||
| 
 | ||||
|             Self { file, image } | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Thumbnail file content.
 | ||||
| @ -155,6 +244,25 @@ impl ThumbnailFileContent { | ||||
|         Self { url, info, encryption_info: Some(Box::new(encryption_info)) } | ||||
|     } | ||||
| 
 | ||||
|     /// Create a `ThumbnailContent` with the given thumbnail source and info.
 | ||||
|     ///
 | ||||
|     /// Returns `None` if no thumbnail was found.
 | ||||
|     fn from_room_message_content( | ||||
|         thumbnail_source: &MediaSource, | ||||
|         thumbnail_info: Option<&ThumbnailInfo>, | ||||
|     ) -> Self { | ||||
|         match thumbnail_source { | ||||
|             MediaSource::Plain(url) => { | ||||
|                 Self::plain(url.to_owned(), thumbnail_info.map(|info| Box::new(info.into()))) | ||||
|             } | ||||
|             MediaSource::Encrypted(file) => Self::encrypted( | ||||
|                 file.url.clone(), | ||||
|                 (&**file).into(), | ||||
|                 thumbnail_info.map(|info| Box::new(info.into())), | ||||
|             ), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Whether the thumbnail file is encrypted.
 | ||||
|     pub fn is_encrypted(&self) -> bool { | ||||
|         self.encryption_info.is_some() | ||||
| @ -180,3 +288,10 @@ impl ThumbnailFileContentInfo { | ||||
|         Self::default() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<&ThumbnailInfo> for ThumbnailFileContentInfo { | ||||
|     fn from(info: &ThumbnailInfo) -> Self { | ||||
|         let ThumbnailInfo { mimetype, size, .. } = info; | ||||
|         Self { mimetype: mimetype.to_owned(), size: size.to_owned() } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -9,6 +9,11 @@ use serde::{de, Deserialize, Serialize}; | ||||
| 
 | ||||
| #[cfg(feature = "unstable-msc3551")] | ||||
| use super::file::{EncryptedContent, FileContent}; | ||||
| #[cfg(feature = "unstable-msc3552")] | ||||
| use super::{ | ||||
|     file::FileContentInfo, | ||||
|     image::{ImageContent, ThumbnailContent, ThumbnailFileContent, ThumbnailFileContentInfo}, | ||||
| }; | ||||
| use crate::{ | ||||
|     serde::{base64::UrlSafe, Base64}, | ||||
|     MxcUri, | ||||
| @ -84,6 +89,18 @@ impl From<&FileContent> for MediaSource { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(feature = "unstable-msc3552")] | ||||
| impl From<&ThumbnailFileContent> for MediaSource { | ||||
|     fn from(content: &ThumbnailFileContent) -> Self { | ||||
|         let ThumbnailFileContent { url, encryption_info, .. } = content; | ||||
|         if let Some(encryption_info) = encryption_info.as_deref() { | ||||
|             Self::Encrypted(Box::new(EncryptedFile::from_extensible_content(url, encryption_info))) | ||||
|         } else { | ||||
|             Self::Plain(url.to_owned()) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Metadata about an image.
 | ||||
| #[derive(Clone, Debug, Default, Deserialize, Serialize)] | ||||
| #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] | ||||
| @ -130,6 +147,46 @@ impl ImageInfo { | ||||
|     pub fn new() -> Self { | ||||
|         Self::default() | ||||
|     } | ||||
| 
 | ||||
|     /// Create an `ImageInfo` from the given file info, image info and thumbnail.
 | ||||
|     #[cfg(feature = "unstable-msc3552")] | ||||
|     pub fn from_extensible_content( | ||||
|         file_info: Option<&FileContentInfo>, | ||||
|         image: &ImageContent, | ||||
|         thumbnail: &[ThumbnailContent], | ||||
|     ) -> Option<Self> { | ||||
|         if file_info.is_none() && image.is_empty() && thumbnail.is_empty() { | ||||
|             None | ||||
|         } else { | ||||
|             let (mimetype, size) = file_info | ||||
|                 .map(|info| (info.mimetype.to_owned(), info.size.to_owned())) | ||||
|                 .unwrap_or_default(); | ||||
|             let ImageContent { height, width } = image.to_owned(); | ||||
|             let (thumbnail_source, thumbnail_info) = thumbnail | ||||
|                 .get(0) | ||||
|                 .map(|thumbnail| { | ||||
|                     let source = (&thumbnail.file).into(); | ||||
|                     let info = ThumbnailInfo::from_extensible_content( | ||||
|                         thumbnail.file.info.as_deref(), | ||||
|                         thumbnail.image.as_deref(), | ||||
|                     ) | ||||
|                     .map(Box::new); | ||||
|                     (Some(source), info) | ||||
|                 }) | ||||
|                 .unwrap_or_default(); | ||||
| 
 | ||||
|             Some(Self { | ||||
|                 height, | ||||
|                 width, | ||||
|                 mimetype, | ||||
|                 size, | ||||
|                 thumbnail_source, | ||||
|                 thumbnail_info, | ||||
|                 #[cfg(feature = "unstable-msc2448")] | ||||
|                 blurhash: None, | ||||
|             }) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Metadata about a thumbnail.
 | ||||
| @ -158,6 +215,24 @@ impl ThumbnailInfo { | ||||
|     pub fn new() -> Self { | ||||
|         Self::default() | ||||
|     } | ||||
| 
 | ||||
|     /// Create a `ThumbnailInfo` with the given file info and image info.
 | ||||
|     ///
 | ||||
|     /// Returns `None` if `file_info` and `image` are `None`.
 | ||||
|     #[cfg(feature = "unstable-msc3552")] | ||||
|     pub fn from_extensible_content( | ||||
|         file_info: Option<&ThumbnailFileContentInfo>, | ||||
|         image: Option<&ImageContent>, | ||||
|     ) -> Option<Self> { | ||||
|         if file_info.is_none() && image.is_none() { | ||||
|             None | ||||
|         } else { | ||||
|             let ThumbnailFileContentInfo { mimetype, size } = | ||||
|                 file_info.map(ToOwned::to_owned).unwrap_or_default(); | ||||
|             let ImageContent { height, width } = image.map(ToOwned::to_owned).unwrap_or_default(); | ||||
|             Some(Self { height, width, mimetype, size }) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// A file sent to a room with end-to-end encryption enabled.
 | ||||
|  | ||||
| @ -12,6 +12,8 @@ use serde_json::Value as JsonValue; | ||||
| use super::{EncryptedFile, ImageInfo, MediaSource, ThumbnailInfo}; | ||||
| #[cfg(feature = "unstable-msc3551")] | ||||
| use crate::events::file::{FileContent, FileContentInfo, FileEventContent}; | ||||
| #[cfg(feature = "unstable-msc3552")] | ||||
| use crate::events::image::{ImageContent, ImageEventContent, ThumbnailContent}; | ||||
| #[cfg(feature = "unstable-msc1767")] | ||||
| use crate::events::{ | ||||
|     emote::EmoteEventContent, | ||||
| @ -213,6 +215,20 @@ impl From<FileEventContent> for RoomMessageEventContent { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(feature = "unstable-msc3552")] | ||||
| impl From<ImageEventContent> for RoomMessageEventContent { | ||||
|     fn from(content: ImageEventContent) -> Self { | ||||
|         let ImageEventContent { message, file, image, thumbnail, caption, relates_to } = content; | ||||
| 
 | ||||
|         Self { | ||||
|             msgtype: MessageType::Image(ImageMessageEventContent::from_extensible_content( | ||||
|                 message, file, image, thumbnail, caption, | ||||
|             )), | ||||
|             relates_to, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(feature = "unstable-msc1767")] | ||||
| impl From<MessageEventContent> for RoomMessageEventContent { | ||||
|     fn from(content: MessageEventContent) -> Self { | ||||
| @ -744,6 +760,10 @@ impl From<&FileContentInfo> for FileInfo { | ||||
| #[derive(Clone, Debug, Deserialize, Serialize)] | ||||
| #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] | ||||
| #[serde(tag = "msgtype", rename = "m.image")] | ||||
| #[cfg_attr(
 | ||||
|     feature = "unstable-msc3552", | ||||
|     serde(from = "content_serde::ImageMessageEventContentDeHelper") | ||||
| )] | ||||
| pub struct ImageMessageEventContent { | ||||
|     /// A textual representation of the image.
 | ||||
|     ///
 | ||||
| @ -758,19 +778,128 @@ pub struct ImageMessageEventContent { | ||||
|     /// Metadata about the image referred to in `url`.
 | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub info: Option<Box<ImageInfo>>, | ||||
| 
 | ||||
|     /// Extensible-event text representation of the message.
 | ||||
|     ///
 | ||||
|     /// If present, this should be preferred over the `body` field.
 | ||||
|     #[cfg(feature = "unstable-msc3552")] | ||||
|     #[serde(flatten, skip_serializing_if = "Option::is_none")] | ||||
|     pub message: Option<MessageContent>, | ||||
| 
 | ||||
|     /// Extensible-event file content of the message.
 | ||||
|     ///
 | ||||
|     /// If present, this should be preferred over the `source` and `info` fields.
 | ||||
|     #[cfg(feature = "unstable-msc3552")] | ||||
|     #[serde(rename = "org.matrix.msc1767.file", skip_serializing_if = "Option::is_none")] | ||||
|     pub file: Option<FileContent>, | ||||
| 
 | ||||
|     /// Extensible-event image info of the message.
 | ||||
|     ///
 | ||||
|     /// If present, this should be preferred over the `info` field.
 | ||||
|     #[cfg(feature = "unstable-msc3552")] | ||||
|     #[serde(rename = "org.matrix.msc1767.image", skip_serializing_if = "Option::is_none")] | ||||
|     pub image: Option<Box<ImageContent>>, | ||||
| 
 | ||||
|     /// Extensible-event thumbnails of the message.
 | ||||
|     ///
 | ||||
|     /// If present, this should be preferred over the `info` field.
 | ||||
|     #[cfg(feature = "unstable-msc3552")] | ||||
|     #[serde(rename = "org.matrix.msc1767.thumbnail", skip_serializing_if = "Option::is_none")] | ||||
|     pub thumbnail: Option<Vec<ThumbnailContent>>, | ||||
| 
 | ||||
|     /// Extensible-event captions of the message.
 | ||||
|     #[cfg(feature = "unstable-msc3552")] | ||||
|     #[serde(
 | ||||
|         rename = "org.matrix.msc1767.caption", | ||||
|         with = "crate::events::message::content_serde::as_vec", | ||||
|         default, | ||||
|         skip_serializing_if = "Option::is_none" | ||||
|     )] | ||||
|     pub caption: Option<MessageContent>, | ||||
| } | ||||
| 
 | ||||
| impl ImageMessageEventContent { | ||||
|     /// Creates a new non-encrypted `RoomImageMessageEventContent` with the given body, url and
 | ||||
|     /// optional extra info.
 | ||||
|     pub fn plain(body: String, url: Box<MxcUri>, info: Option<Box<ImageInfo>>) -> Self { | ||||
|         Self { body, source: MediaSource::Plain(url), info } | ||||
|         Self { | ||||
|             #[cfg(feature = "unstable-msc3552")] | ||||
|             message: Some(MessageContent::plain(body.clone())), | ||||
|             #[cfg(feature = "unstable-msc3552")] | ||||
|             file: Some(FileContent::plain( | ||||
|                 url.clone(), | ||||
|                 info.as_deref().map(|info| Box::new(info.into())), | ||||
|             )), | ||||
|             #[cfg(feature = "unstable-msc3552")] | ||||
|             image: Some(Box::new(info.as_deref().map_or_else(ImageContent::default, Into::into))), | ||||
|             #[cfg(feature = "unstable-msc3552")] | ||||
|             thumbnail: info | ||||
|                 .as_deref() | ||||
|                 .and_then(|info| { | ||||
|                     ThumbnailContent::from_room_message_content( | ||||
|                         info.thumbnail_source.as_ref(), | ||||
|                         info.thumbnail_info.as_deref(), | ||||
|                     ) | ||||
|                 }) | ||||
|                 .map(|thumbnail| vec![thumbnail]), | ||||
|             #[cfg(feature = "unstable-msc3552")] | ||||
|             caption: None, | ||||
|             body, | ||||
|             source: MediaSource::Plain(url), | ||||
|             info, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Creates a new encrypted `RoomImageMessageEventContent` with the given body and encrypted
 | ||||
|     /// file.
 | ||||
|     pub fn encrypted(body: String, file: EncryptedFile) -> Self { | ||||
|         Self { body, source: MediaSource::Encrypted(Box::new(file)), info: None } | ||||
|         Self { | ||||
|             #[cfg(feature = "unstable-msc3552")] | ||||
|             message: Some(MessageContent::plain(body.clone())), | ||||
|             #[cfg(feature = "unstable-msc3552")] | ||||
|             file: Some(FileContent::encrypted(file.url.clone(), (&file).into(), None)), | ||||
|             #[cfg(feature = "unstable-msc3552")] | ||||
|             image: Some(Box::new(ImageContent::default())), | ||||
|             #[cfg(feature = "unstable-msc3552")] | ||||
|             thumbnail: None, | ||||
|             #[cfg(feature = "unstable-msc3552")] | ||||
|             caption: None, | ||||
|             body, | ||||
|             source: MediaSource::Encrypted(Box::new(file)), | ||||
|             info: None, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Create a new `ImageMessageEventContent` with the given message, file info, image info,
 | ||||
|     /// thumbnails and captions.
 | ||||
|     #[cfg(feature = "unstable-msc3552")] | ||||
|     pub fn from_extensible_content( | ||||
|         message: MessageContent, | ||||
|         file: FileContent, | ||||
|         image: Box<ImageContent>, | ||||
|         thumbnail: Vec<ThumbnailContent>, | ||||
|         caption: Option<MessageContent>, | ||||
|     ) -> Self { | ||||
|         let body = if let Some(body) = message.find_plain() { | ||||
|             body.to_owned() | ||||
|         } else { | ||||
|             message[0].body.clone() | ||||
|         }; | ||||
|         let source = (&file).into(); | ||||
|         let info = ImageInfo::from_extensible_content(file.info.as_deref(), &image, &thumbnail) | ||||
|             .map(Box::new); | ||||
|         let thumbnail = if thumbnail.is_empty() { None } else { Some(thumbnail) }; | ||||
| 
 | ||||
|         Self { | ||||
|             message: Some(message), | ||||
|             file: Some(file), | ||||
|             image: Some(image), | ||||
|             thumbnail, | ||||
|             caption, | ||||
|             body, | ||||
|             source, | ||||
|             info, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -5,6 +5,8 @@ use serde_json::value::RawValue as RawJsonValue; | ||||
| 
 | ||||
| #[cfg(feature = "unstable-msc3551")] | ||||
| use super::{FileContent, FileInfo, FileMessageEventContent, MediaSource, MessageContent}; | ||||
| #[cfg(feature = "unstable-msc3552")] | ||||
| use super::{ImageContent, ImageInfo, ImageMessageEventContent, ThumbnailContent}; | ||||
| use super::{MessageType, Relation, RoomMessageEventContent}; | ||||
| use crate::serde::from_raw_json_value; | ||||
| 
 | ||||
| @ -104,3 +106,84 @@ impl From<FileMessageEventContentDeHelper> for FileMessageEventContent { | ||||
|         Self { body, filename, source, info, message, file } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Helper struct for deserializing `ImageMessageEventContent` with stable and unstable field names.
 | ||||
| ///
 | ||||
| /// It's not possible to use the `alias` attribute of serde because of
 | ||||
| /// https://github.com/serde-rs/serde/issues/1504.
 | ||||
| #[derive(Clone, Debug, Deserialize)] | ||||
| #[cfg(feature = "unstable-msc3552")] | ||||
| pub struct ImageMessageEventContentDeHelper { | ||||
|     /// A textual representation of the image.
 | ||||
|     pub body: String, | ||||
| 
 | ||||
|     /// The source of the image.
 | ||||
|     #[serde(flatten)] | ||||
|     pub source: MediaSource, | ||||
| 
 | ||||
|     /// Metadata about the image referred to in `source`.
 | ||||
|     pub info: Option<Box<ImageInfo>>, | ||||
| 
 | ||||
|     /// Extensible-event text representation of the message.
 | ||||
|     #[serde(flatten)] | ||||
|     pub message: Option<MessageContent>, | ||||
| 
 | ||||
|     /// Extensible-event file content of the message, with unstable name.
 | ||||
|     #[serde(rename = "m.file")] | ||||
|     pub file_stable: Option<FileContent>, | ||||
| 
 | ||||
|     /// Extensible-event file content of the message, with unstable name.
 | ||||
|     #[serde(rename = "org.matrix.msc1767.file")] | ||||
|     pub file_unstable: Option<FileContent>, | ||||
| 
 | ||||
|     /// Extensible-event image info of the message, with stable name.
 | ||||
|     #[serde(rename = "m.image")] | ||||
|     pub image_stable: Option<Box<ImageContent>>, | ||||
| 
 | ||||
|     /// Extensible-event image info of the message, with unstable name.
 | ||||
|     #[serde(rename = "org.matrix.msc1767.image")] | ||||
|     pub image_unstable: Option<Box<ImageContent>>, | ||||
| 
 | ||||
|     /// Extensible-event thumbnails of the message, with stable name.
 | ||||
|     #[serde(rename = "m.thumbnail")] | ||||
|     pub thumbnail_stable: Option<Vec<ThumbnailContent>>, | ||||
| 
 | ||||
|     /// Extensible-event thumbnails of the message, with unstable name.
 | ||||
|     #[serde(rename = "org.matrix.msc1767.thumbnail")] | ||||
|     pub thumbnail_unstable: Option<Vec<ThumbnailContent>>, | ||||
| 
 | ||||
|     /// Extensible-event captions of the message, with stable name.
 | ||||
|     #[serde(rename = "m.caption")] | ||||
|     pub caption_stable: Option<MessageContent>, | ||||
| 
 | ||||
|     /// Extensible-event captions of the message, with unstable name.
 | ||||
|     #[serde(rename = "org.matrix.msc1767.caption")] | ||||
|     pub caption_unstable: Option<MessageContent>, | ||||
| } | ||||
| 
 | ||||
| #[cfg(feature = "unstable-msc3552")] | ||||
| impl From<ImageMessageEventContentDeHelper> for ImageMessageEventContent { | ||||
|     fn from(helper: ImageMessageEventContentDeHelper) -> Self { | ||||
|         let ImageMessageEventContentDeHelper { | ||||
|             body, | ||||
|             source, | ||||
|             info, | ||||
|             message, | ||||
|             file_stable, | ||||
|             file_unstable, | ||||
|             image_stable, | ||||
|             image_unstable, | ||||
|             thumbnail_stable, | ||||
|             thumbnail_unstable, | ||||
|             caption_stable, | ||||
|             caption_unstable, | ||||
|         } = helper; | ||||
| 
 | ||||
|         let file = file_stable.or(file_unstable); | ||||
|         let image = image_stable.or(image_unstable); | ||||
|         let thumbnail = thumbnail_stable.or(thumbnail_unstable); | ||||
|         let caption = caption_stable.or(caption_unstable); | ||||
| 
 | ||||
|         Self { body, source, info, message, file, image, thumbnail, caption } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -13,8 +13,10 @@ use ruma_common::{ | ||||
|         }, | ||||
|         message::MessageContent, | ||||
|         room::{ | ||||
|             message::{InReplyTo, Relation}, | ||||
|             JsonWebKeyInit, | ||||
|             message::{ | ||||
|                 ImageMessageEventContent, InReplyTo, MessageType, Relation, RoomMessageEventContent, | ||||
|             }, | ||||
|             JsonWebKeyInit, MediaSource, | ||||
|         }, | ||||
|         AnyMessageLikeEvent, MessageLikeEvent, MessageLikeUnsigned, | ||||
|     }, | ||||
| @ -190,7 +192,7 @@ fn image_event_serialization() { | ||||
| #[test] | ||||
| fn plain_content_deserialization() { | ||||
|     let json_data = json!({ | ||||
|         "org.matrix.msc1767.text": "Upload: my_cat.png", | ||||
|         "m.text": "Upload: my_cat.png", | ||||
|         "m.file": { | ||||
|             "url": "mxc://notareal.hs/abcdef", | ||||
|         }, | ||||
| @ -318,3 +320,87 @@ fn message_event_deserialization() { | ||||
|             && unsigned.is_empty() | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn room_message_serialization() { | ||||
|     let message_event_content = | ||||
|         RoomMessageEventContent::new(MessageType::Image(ImageMessageEventContent::plain( | ||||
|             "Upload: my_image.jpg".to_owned(), | ||||
|             mxc_uri!("mxc://notareal.hs/file").to_owned(), | ||||
|             None, | ||||
|         ))); | ||||
| 
 | ||||
|     assert_eq!( | ||||
|         to_json_value(&message_event_content).unwrap(), | ||||
|         json!({ | ||||
|             "body": "Upload: my_image.jpg", | ||||
|             "url": "mxc://notareal.hs/file", | ||||
|             "msgtype": "m.image", | ||||
|             "org.matrix.msc1767.text": "Upload: my_image.jpg", | ||||
|             "org.matrix.msc1767.file": { | ||||
|                 "url": "mxc://notareal.hs/file", | ||||
|             }, | ||||
|             "org.matrix.msc1767.image": {}, | ||||
|         }) | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn room_message_stable_deserialization() { | ||||
|     let json_data = json!({ | ||||
|         "body": "Upload: my_image.jpg", | ||||
|         "url": "mxc://notareal.hs/file", | ||||
|         "msgtype": "m.image", | ||||
|         "m.text": "Upload: my_image.jpg", | ||||
|         "m.file": { | ||||
|             "url": "mxc://notareal.hs/file", | ||||
|         }, | ||||
|         "m.image": {}, | ||||
|     }); | ||||
| 
 | ||||
|     let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap(); | ||||
|     assert_matches!(event_content.msgtype, MessageType::Image(_)); | ||||
|     if let MessageType::Image(content) = event_content.msgtype { | ||||
|         assert_eq!(content.body, "Upload: my_image.jpg"); | ||||
|         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: my_image.jpg"); | ||||
|         let file = content.file.unwrap(); | ||||
|         assert_eq!(file.url, "mxc://notareal.hs/file"); | ||||
|         assert!(!file.is_encrypted()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn room_message_unstable_deserialization() { | ||||
|     let json_data = json!({ | ||||
|         "body": "Upload: my_image.jpg", | ||||
|         "url": "mxc://notareal.hs/file", | ||||
|         "msgtype": "m.image", | ||||
|         "org.matrix.msc1767.text": "Upload: my_image.jpg", | ||||
|         "org.matrix.msc1767.file": { | ||||
|             "url": "mxc://notareal.hs/file", | ||||
|         }, | ||||
|         "org.matrix.msc1767.image": {}, | ||||
|     }); | ||||
| 
 | ||||
|     let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap(); | ||||
|     assert_matches!(event_content.msgtype, MessageType::Image(_)); | ||||
|     if let MessageType::Image(content) = event_content.msgtype { | ||||
|         assert_eq!(content.body, "Upload: my_image.jpg"); | ||||
|         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: my_image.jpg"); | ||||
|         let file = content.file.unwrap(); | ||||
|         assert_eq!(file.url, "mxc://notareal.hs/file"); | ||||
|         assert!(!file.is_encrypted()); | ||||
|     } | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user