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.
|
`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`.
|
- `CallMemberStateKey` (instead of `OwnedUserId`) is now used as the state key type for `CallMemberEventContent`.
|
||||||
This guarantees correct formatting of the event key.
|
This guarantees correct formatting of the event key.
|
||||||
|
- Add helpers for captions on audio, file, image and video messages.
|
||||||
|
|
||||||
Breaking changes:
|
Breaking changes:
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ mod file;
|
|||||||
mod image;
|
mod image;
|
||||||
mod key_verification_request;
|
mod key_verification_request;
|
||||||
mod location;
|
mod location;
|
||||||
|
mod media_caption;
|
||||||
mod notice;
|
mod notice;
|
||||||
mod relation;
|
mod relation;
|
||||||
pub(crate) mod relation_serde;
|
pub(crate) mod relation_serde;
|
||||||
|
@ -5,7 +5,10 @@ use ruma_common::OwnedMxcUri;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::FormattedBody;
|
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.
|
/// The payload for an audio message.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
@ -88,6 +91,21 @@ impl AudioMessageEventContent {
|
|||||||
pub fn info(self, info: impl Into<Option<Box<AudioInfo>>>) -> Self {
|
pub fn info(self, info: impl Into<Option<Box<AudioInfo>>>) -> Self {
|
||||||
Self { info: info.into(), ..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.
|
/// Metadata about an audio clip.
|
||||||
|
@ -3,7 +3,10 @@ use ruma_common::OwnedMxcUri;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::FormattedBody;
|
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.
|
/// The payload for a file message.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
@ -60,6 +63,21 @@ impl FileMessageEventContent {
|
|||||||
pub fn info(self, info: impl Into<Option<Box<FileInfo>>>) -> Self {
|
pub fn info(self, info: impl Into<Option<Box<FileInfo>>>) -> Self {
|
||||||
Self { info: info.into(), ..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.
|
/// Metadata about a file.
|
||||||
|
@ -2,7 +2,10 @@ use ruma_common::OwnedMxcUri;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::FormattedBody;
|
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.
|
/// The payload for an image message.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
@ -59,4 +62,19 @@ impl ImageMessageEventContent {
|
|||||||
pub fn info(self, info: impl Into<Option<Box<ImageInfo>>>) -> Self {
|
pub fn info(self, info: impl Into<Option<Box<ImageInfo>>>) -> Self {
|
||||||
Self { info: info.into(), ..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 serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::FormattedBody;
|
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.
|
/// The payload for a video message.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
@ -62,6 +65,21 @@ impl VideoMessageEventContent {
|
|||||||
pub fn info(self, info: impl Into<Option<Box<VideoInfo>>>) -> Self {
|
pub fn info(self, info: impl Into<Option<Box<VideoInfo>>>) -> Self {
|
||||||
Self { info: info.into(), ..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.
|
/// Metadata about a video.
|
||||||
|
@ -12,7 +12,7 @@ use ruma_events::{
|
|||||||
room::{
|
room::{
|
||||||
message::{
|
message::{
|
||||||
AddMentions, AudioMessageEventContent, EmoteMessageEventContent,
|
AddMentions, AudioMessageEventContent, EmoteMessageEventContent,
|
||||||
FileMessageEventContent, ForwardThread, ImageMessageEventContent,
|
FileMessageEventContent, FormattedBody, ForwardThread, ImageMessageEventContent,
|
||||||
KeyVerificationRequestEventContent, MessageType, OriginalRoomMessageEvent,
|
KeyVerificationRequestEventContent, MessageType, OriginalRoomMessageEvent,
|
||||||
OriginalSyncRoomMessageEvent, Relation, ReplyWithinThread, RoomMessageEventContent,
|
OriginalSyncRoomMessageEvent, Relation, ReplyWithinThread, RoomMessageEventContent,
|
||||||
TextMessageEventContent, VideoMessageEventContent,
|
TextMessageEventContent, VideoMessageEventContent,
|
||||||
@ -900,8 +900,9 @@ fn audio_msgtype_deserialization() {
|
|||||||
let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
|
let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
|
||||||
assert_matches!(event_content.msgtype, MessageType::Audio(content));
|
assert_matches!(event_content.msgtype, MessageType::Audio(content));
|
||||||
assert_eq!(content.body, "Upload: my_song.mp3");
|
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_eq!(url, "mxc://notareal.hs/file");
|
||||||
|
assert!(content.caption().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -983,8 +984,9 @@ fn file_msgtype_plain_content_deserialization() {
|
|||||||
let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
|
let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
|
||||||
assert_matches!(event_content.msgtype, MessageType::File(content));
|
assert_matches!(event_content.msgtype, MessageType::File(content));
|
||||||
assert_eq!(content.body, "Upload: my_file.txt");
|
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_eq!(url, "mxc://notareal.hs/file");
|
||||||
|
assert!(content.caption().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1045,8 +1047,9 @@ fn image_msgtype_deserialization() {
|
|||||||
let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
|
let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
|
||||||
assert_matches!(event_content.msgtype, MessageType::Image(content));
|
assert_matches!(event_content.msgtype, MessageType::Image(content));
|
||||||
assert_eq!(content.body, "Upload: my_image.jpg");
|
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_eq!(url, "mxc://notareal.hs/file");
|
||||||
|
assert!(content.caption().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "unstable-msc3488"))]
|
#[cfg(not(feature = "unstable-msc3488"))]
|
||||||
@ -1202,8 +1205,9 @@ fn video_msgtype_deserialization() {
|
|||||||
let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
|
let event_content = from_json_value::<RoomMessageEventContent>(json_data).unwrap();
|
||||||
assert_matches!(event_content.msgtype, MessageType::Video(content));
|
assert_matches!(event_content.msgtype, MessageType::Video(content));
|
||||||
assert_eq!(content.body, "Upload: my_video.mp4");
|
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_eq!(url, "mxc://notareal.hs/file");
|
||||||
|
assert!(content.caption().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1330,3 +1334,106 @@ fn invalid_replacement() {
|
|||||||
assert_matches!(&data, Cow::Borrowed(_)); // data is stored in JSON form because it's invalid
|
assert_matches!(&data, Cow::Borrowed(_)); // data is stored in JSON form because it's invalid
|
||||||
assert_eq!(JsonValue::Object(data.into_owned()), relation);
|
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