events: Add helpers for media captions to audio, file, image and video messages

This commit is contained in:
Doug 2024-10-07 10:39:45 +01:00 committed by strawberry
parent 0286bcfa2f
commit e0db68241d
8 changed files with 212 additions and 9 deletions

View File

@ -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:

View File

@ -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;

View File

@ -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.

View File

@ -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.

View 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())
}
}

View 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()
}

View File

@ -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.

View File

@ -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())
);
}