events: Add helpers for media captions to audio, file, image and video messages
This commit is contained in:
parent
0286bcfa2f
commit
e0db68241d
@ -23,6 +23,7 @@ The new format (Session) is required to reliably display the call member count (
|
||||
`CallMemberEventContent` is now an enum to model the two different formats.
|
||||
- `CallMemberStateKey` (instead of `OwnedUserId`) is now used as the state key type for `CallMemberEventContent`.
|
||||
This guarantees correct formatting of the event key.
|
||||
- Add helpers for captions on audio, file, image and video messages.
|
||||
|
||||
Breaking changes:
|
||||
|
||||
|
@ -30,6 +30,7 @@ mod file;
|
||||
mod image;
|
||||
mod key_verification_request;
|
||||
mod location;
|
||||
mod media_caption;
|
||||
mod notice;
|
||||
mod relation;
|
||||
pub(crate) mod relation_serde;
|
||||
|
@ -5,7 +5,10 @@ use ruma_common::OwnedMxcUri;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::FormattedBody;
|
||||
use crate::room::{EncryptedFile, MediaSource};
|
||||
use crate::room::{
|
||||
message::media_caption::{caption, formatted_caption},
|
||||
EncryptedFile, MediaSource,
|
||||
};
|
||||
|
||||
/// The payload for an audio message.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
@ -88,6 +91,21 @@ impl AudioMessageEventContent {
|
||||
pub fn info(self, info: impl Into<Option<Box<AudioInfo>>>) -> Self {
|
||||
Self { info: info.into(), ..self }
|
||||
}
|
||||
|
||||
/// Returns the caption for the audio as defined by the [spec](https://spec.matrix.org/latest/client-server-api/#media-captions).
|
||||
///
|
||||
/// In short, this is the `body` field if the `filename` field exists and has a different value,
|
||||
/// otherwise the media file does not have a caption.
|
||||
pub fn caption(&self) -> Option<&str> {
|
||||
caption(&self.body, self.filename.as_deref())
|
||||
}
|
||||
|
||||
/// Returns the formatted caption for the audio as defined by the [spec](https://spec.matrix.org/latest/client-server-api/#media-captions).
|
||||
///
|
||||
/// This is the same as `caption`, but returns the formatted body instead of the plain body.
|
||||
pub fn formatted_caption(&self) -> Option<&FormattedBody> {
|
||||
formatted_caption(&self.body, self.formatted.as_ref(), self.filename.as_deref())
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata about an audio clip.
|
||||
|
@ -3,7 +3,10 @@ use ruma_common::OwnedMxcUri;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::FormattedBody;
|
||||
use crate::room::{EncryptedFile, MediaSource, ThumbnailInfo};
|
||||
use crate::room::{
|
||||
message::media_caption::{caption, formatted_caption},
|
||||
EncryptedFile, MediaSource, ThumbnailInfo,
|
||||
};
|
||||
|
||||
/// The payload for a file message.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
@ -60,6 +63,21 @@ impl FileMessageEventContent {
|
||||
pub fn info(self, info: impl Into<Option<Box<FileInfo>>>) -> Self {
|
||||
Self { info: info.into(), ..self }
|
||||
}
|
||||
|
||||
/// Returns the caption of the media file as defined by the [spec](https://spec.matrix.org/latest/client-server-api/#media-captions).
|
||||
///
|
||||
/// In short, this is the `body` field if the `filename` field exists and has a different value,
|
||||
/// otherwise the media file does not have a caption.
|
||||
pub fn caption(&self) -> Option<&str> {
|
||||
caption(&self.body, self.filename.as_deref())
|
||||
}
|
||||
|
||||
/// Returns the formatted caption of the media file as defined by the [spec](https://spec.matrix.org/latest/client-server-api/#media-captions).
|
||||
///
|
||||
/// This is the same as `caption`, but returns the formatted body instead of the plain body.
|
||||
pub fn formatted_caption(&self) -> Option<&FormattedBody> {
|
||||
formatted_caption(&self.body, self.formatted.as_ref(), self.filename.as_deref())
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata about a file.
|
||||
|
@ -2,7 +2,10 @@ use ruma_common::OwnedMxcUri;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::FormattedBody;
|
||||
use crate::room::{EncryptedFile, ImageInfo, MediaSource};
|
||||
use crate::room::{
|
||||
message::media_caption::{caption, formatted_caption},
|
||||
EncryptedFile, ImageInfo, MediaSource,
|
||||
};
|
||||
|
||||
/// The payload for an image message.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
@ -59,4 +62,19 @@ impl ImageMessageEventContent {
|
||||
pub fn info(self, info: impl Into<Option<Box<ImageInfo>>>) -> Self {
|
||||
Self { info: info.into(), ..self }
|
||||
}
|
||||
|
||||
/// Returns the caption for the image as defined by the [spec](https://spec.matrix.org/latest/client-server-api/#media-captions).
|
||||
///
|
||||
/// In short, this is the `body` field if the `filename` field exists and has a different value,
|
||||
/// otherwise the media file does not have a caption.
|
||||
pub fn caption(&self) -> Option<&str> {
|
||||
caption(&self.body, self.filename.as_deref())
|
||||
}
|
||||
|
||||
/// Returns the formatted caption for the image as defined by the [spec](https://spec.matrix.org/latest/client-server-api/#media-captions).
|
||||
///
|
||||
/// This is the same as `caption`, but returns the formatted body instead of the plain body.
|
||||
pub fn formatted_caption(&self) -> Option<&FormattedBody> {
|
||||
formatted_caption(&self.body, self.formatted.as_ref(), self.filename.as_deref())
|
||||
}
|
||||
}
|
||||
|
22
crates/ruma-events/src/room/message/media_caption.rs
Normal file
22
crates/ruma-events/src/room/message/media_caption.rs
Normal file
@ -0,0 +1,22 @@
|
||||
//! Reusable methods for captioning media files.
|
||||
|
||||
use crate::room::message::FormattedBody;
|
||||
|
||||
/// Computes the caption of a media file as defined by the [spec](https://spec.matrix.org/latest/client-server-api/#media-captions).
|
||||
///
|
||||
/// In short, this is the `body` field if the `filename` field exists and has a different value,
|
||||
/// otherwise the media file does not have a caption.
|
||||
pub(crate) fn caption<'a>(body: &'a str, filename: Option<&str>) -> Option<&'a str> {
|
||||
filename.is_some_and(|filename| body != filename).then_some(body)
|
||||
}
|
||||
|
||||
/// Computes the formatted caption of a media file as defined by the [spec](https://spec.matrix.org/latest/client-server-api/#media-captions).
|
||||
///
|
||||
/// This is the same as `caption`, but returns the formatted body instead of the plain body.
|
||||
pub(crate) fn formatted_caption<'a>(
|
||||
body: &str,
|
||||
formatted: Option<&'a FormattedBody>,
|
||||
filename: Option<&str>,
|
||||
) -> Option<&'a FormattedBody> {
|
||||
filename.is_some_and(|filename| body != filename).then_some(formatted).flatten()
|
||||
}
|
@ -5,7 +5,10 @@ use ruma_common::OwnedMxcUri;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::FormattedBody;
|
||||
use crate::room::{EncryptedFile, MediaSource, ThumbnailInfo};
|
||||
use crate::room::{
|
||||
message::media_caption::{caption, formatted_caption},
|
||||
EncryptedFile, MediaSource, ThumbnailInfo,
|
||||
};
|
||||
|
||||
/// The payload for a video message.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
@ -62,6 +65,21 @@ impl VideoMessageEventContent {
|
||||
pub fn info(self, info: impl Into<Option<Box<VideoInfo>>>) -> Self {
|
||||
Self { info: info.into(), ..self }
|
||||
}
|
||||
|
||||
/// Returns the caption of the video as defined by the [spec](https://spec.matrix.org/latest/client-server-api/#media-captions).
|
||||
///
|
||||
/// In short, this is the `body` field if the `filename` field exists and has a different value,
|
||||
/// otherwise the media file does not have a caption.
|
||||
pub fn caption(&self) -> Option<&str> {
|
||||
caption(&self.body, self.filename.as_deref())
|
||||
}
|
||||
|
||||
/// Returns the formatted caption of the video as defined by the [spec](https://spec.matrix.org/latest/client-server-api/#media-captions).
|
||||
///
|
||||
/// This is the same as `caption`, but returns the formatted body instead of the plain body.
|
||||
pub fn formatted_caption(&self) -> Option<&FormattedBody> {
|
||||
formatted_caption(&self.body, self.formatted.as_ref(), self.filename.as_deref())
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata about a video.
|
||||
|
@ -12,7 +12,7 @@ use ruma_events::{
|
||||
room::{
|
||||
message::{
|
||||
AddMentions, AudioMessageEventContent, EmoteMessageEventContent,
|
||||
FileMessageEventContent, ForwardThread, ImageMessageEventContent,
|
||||
FileMessageEventContent, FormattedBody, ForwardThread, ImageMessageEventContent,
|
||||
KeyVerificationRequestEventContent, MessageType, OriginalRoomMessageEvent,
|
||||
OriginalSyncRoomMessageEvent, Relation, ReplyWithinThread, RoomMessageEventContent,
|
||||
TextMessageEventContent, VideoMessageEventContent,
|
||||
@ -900,8 +900,9 @@ fn audio_msgtype_deserialization() {
|
||||
let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
|
||||
assert_matches!(event_content.msgtype, MessageType::Audio(content));
|
||||
assert_eq!(content.body, "Upload: my_song.mp3");
|
||||
assert_matches!(content.source, MediaSource::Plain(url));
|
||||
assert_matches!(&content.source, MediaSource::Plain(url));
|
||||
assert_eq!(url, "mxc://notareal.hs/file");
|
||||
assert!(content.caption().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -983,8 +984,9 @@ fn file_msgtype_plain_content_deserialization() {
|
||||
let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
|
||||
assert_matches!(event_content.msgtype, MessageType::File(content));
|
||||
assert_eq!(content.body, "Upload: my_file.txt");
|
||||
assert_matches!(content.source, MediaSource::Plain(url));
|
||||
assert_matches!(&content.source, MediaSource::Plain(url));
|
||||
assert_eq!(url, "mxc://notareal.hs/file");
|
||||
assert!(content.caption().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1045,8 +1047,9 @@ fn image_msgtype_deserialization() {
|
||||
let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
|
||||
assert_matches!(event_content.msgtype, MessageType::Image(content));
|
||||
assert_eq!(content.body, "Upload: my_image.jpg");
|
||||
assert_matches!(content.source, MediaSource::Plain(url));
|
||||
assert_matches!(&content.source, MediaSource::Plain(url));
|
||||
assert_eq!(url, "mxc://notareal.hs/file");
|
||||
assert!(content.caption().is_none());
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "unstable-msc3488"))]
|
||||
@ -1202,8 +1205,9 @@ fn video_msgtype_deserialization() {
|
||||
let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
|
||||
assert_matches!(event_content.msgtype, MessageType::Video(content));
|
||||
assert_eq!(content.body, "Upload: my_video.mp4");
|
||||
assert_matches!(content.source, MediaSource::Plain(url));
|
||||
assert_matches!(&content.source, MediaSource::Plain(url));
|
||||
assert_eq!(url, "mxc://notareal.hs/file");
|
||||
assert!(content.caption().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1330,3 +1334,106 @@ fn invalid_replacement() {
|
||||
assert_matches!(&data, Cow::Borrowed(_)); // data is stored in JSON form because it's invalid
|
||||
assert_eq!(JsonValue::Object(data.into_owned()), relation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_audio_caption() {
|
||||
let mut content = AudioMessageEventContent::plain(
|
||||
"my_sound.ogg".to_owned(),
|
||||
mxc_uri!("mxc://notareal.hs/abcdef").to_owned(),
|
||||
);
|
||||
assert!(content.caption().is_none());
|
||||
assert!(content.formatted_caption().is_none());
|
||||
|
||||
content.filename = Some("my_sound.ogg".to_owned());
|
||||
assert!(content.caption().is_none());
|
||||
assert!(content.formatted_caption().is_none());
|
||||
|
||||
content.body = "This was a great podcast episode".to_owned();
|
||||
assert_eq!(content.caption(), Some("This was a great podcast episode"));
|
||||
assert!(content.formatted_caption().is_none());
|
||||
|
||||
content.formatted =
|
||||
Some(FormattedBody::html("This was a <em>great</em> podcast episode".to_owned()));
|
||||
assert_eq!(content.caption(), Some("This was a great podcast episode"));
|
||||
assert_eq!(
|
||||
content.formatted_caption().map(|f| f.body.clone()),
|
||||
Some("This was a <em>great</em> podcast episode".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_caption() {
|
||||
let mut content = FileMessageEventContent::plain(
|
||||
"my_file.txt".to_owned(),
|
||||
mxc_uri!("mxc://notareal.hs/abcdef").to_owned(),
|
||||
);
|
||||
assert!(content.caption().is_none());
|
||||
assert!(content.formatted_caption().is_none());
|
||||
|
||||
content.filename = Some("my_file.txt".to_owned());
|
||||
assert!(content.caption().is_none());
|
||||
assert!(content.formatted_caption().is_none());
|
||||
|
||||
content.body = "Please check these notes".to_owned();
|
||||
assert_eq!(content.caption(), Some("Please check these notes"));
|
||||
assert!(content.formatted_caption().is_none());
|
||||
|
||||
content.formatted =
|
||||
Some(FormattedBody::html("<strong>Please check these notes</strong>".to_owned()));
|
||||
assert_eq!(content.caption(), Some("Please check these notes"));
|
||||
assert_eq!(
|
||||
content.formatted_caption().map(|f| f.body.clone()),
|
||||
Some("<strong>Please check these notes</strong>".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_image_caption() {
|
||||
let mut content = ImageMessageEventContent::plain(
|
||||
"my_image.jpg".to_owned(),
|
||||
mxc_uri!("mxc://notareal.hs/abcdef").to_owned(),
|
||||
);
|
||||
assert!(content.caption().is_none());
|
||||
assert!(content.formatted_caption().is_none());
|
||||
|
||||
content.filename = Some("my_image.jpg".to_owned());
|
||||
assert!(content.caption().is_none());
|
||||
assert!(content.formatted_caption().is_none());
|
||||
|
||||
content.body = "Check it out 😎".to_owned();
|
||||
assert_eq!(content.caption(), Some("Check it out 😎"));
|
||||
assert!(content.formatted_caption().is_none());
|
||||
|
||||
content.formatted = Some(FormattedBody::html("<h3>Check it out 😎</h3>".to_owned()));
|
||||
assert_eq!(content.caption(), Some("Check it out 😎"));
|
||||
assert_eq!(
|
||||
content.formatted_caption().map(|f| f.body.clone()),
|
||||
Some("<h3>Check it out 😎</h3>".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_video_caption() {
|
||||
let mut content = VideoMessageEventContent::plain(
|
||||
"my_video.mp4".to_owned(),
|
||||
mxc_uri!("mxc://notareal.hs/abcdef").to_owned(),
|
||||
);
|
||||
assert!(content.caption().is_none());
|
||||
assert!(content.formatted_caption().is_none());
|
||||
|
||||
content.filename = Some("my_video.mp4".to_owned());
|
||||
assert!(content.caption().is_none());
|
||||
assert!(content.formatted_caption().is_none());
|
||||
|
||||
content.body = "You missed a great evening".to_owned();
|
||||
assert_eq!(content.caption(), Some("You missed a great evening"));
|
||||
assert!(content.formatted_caption().is_none());
|
||||
|
||||
content.formatted =
|
||||
Some(FormattedBody::html("You missed a <strong>great</strong> evening".to_owned()));
|
||||
assert_eq!(content.caption(), Some("You missed a great evening"));
|
||||
assert_eq!(
|
||||
content.formatted_caption().map(|f| f.body.clone()),
|
||||
Some("You missed a <strong>great</strong> evening".to_owned())
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user