use js_int::UInt;
use ruma_macros::EventContent;
use serde::{Deserialize, Serialize};
use super::{
file::{EncryptedContent, FileContent},
message::MessageContent,
room::{
message::{ImageMessageEventContent, Relation},
ImageInfo, MediaSource, ThumbnailInfo,
},
};
use crate::OwnedMxcUri;
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[ruma_event(type = "m.image", kind = MessageLike)]
pub struct ImageEventContent {
#[serde(flatten)]
pub message: MessageContent,
#[serde(rename = "m.file")]
pub file: FileContent,
#[serde(rename = "m.image")]
pub image: Box<ImageContent>,
#[serde(rename = "m.thumbnail", default, skip_serializing_if = "Vec::is_empty")]
pub thumbnail: Vec<ThumbnailContent>,
#[serde(
rename = "m.caption",
with = "super::message::content_serde::as_vec",
default,
skip_serializing_if = "Option::is_none"
)]
pub caption: Option<MessageContent>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub relates_to: Option<Relation>,
}
impl ImageEventContent {
pub fn plain(message: impl Into<String>, file: FileContent) -> Self {
Self {
message: MessageContent::plain(message),
file,
image: Default::default(),
thumbnail: Default::default(),
caption: Default::default(),
relates_to: None,
}
}
pub fn with_message(message: MessageContent, file: FileContent) -> Self {
Self {
message,
file,
image: Default::default(),
thumbnail: Default::default(),
caption: Default::default(),
relates_to: None,
}
}
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 }
}
}
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct ImageContent {
#[serde(skip_serializing_if = "Option::is_none")]
pub height: Option<UInt>,
#[serde(skip_serializing_if = "Option::is_none")]
pub width: Option<UInt>,
}
impl ImageContent {
pub fn new() -> Self {
Self::default()
}
pub fn with_size(width: UInt, height: UInt) -> Self {
Self { height: Some(height), width: Some(width) }
}
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() }
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct ThumbnailContent {
#[serde(flatten)]
pub file: ThumbnailFileContent,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub image: Option<Box<ImageContent>>,
}
impl ThumbnailContent {
pub fn new(file: ThumbnailFileContent, image: Option<Box<ImageContent>>) -> Self {
Self { file, image }
}
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 }
})
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct ThumbnailFileContent {
pub url: OwnedMxcUri,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub info: Option<Box<ThumbnailFileContentInfo>>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub encryption_info: Option<Box<EncryptedContent>>,
}
impl ThumbnailFileContent {
pub fn plain(url: OwnedMxcUri, info: Option<Box<ThumbnailFileContentInfo>>) -> Self {
Self { url, info, encryption_info: None }
}
pub fn encrypted(
url: OwnedMxcUri,
encryption_info: EncryptedContent,
info: Option<Box<ThumbnailFileContentInfo>>,
) -> Self {
Self { url, info, encryption_info: Some(Box::new(encryption_info)) }
}
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())),
),
}
}
pub fn is_encrypted(&self) -> bool {
self.encryption_info.is_some()
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct ThumbnailFileContentInfo {
#[serde(skip_serializing_if = "Option::is_none")]
pub mimetype: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<UInt>,
}
impl ThumbnailFileContentInfo {
pub fn new() -> Self {
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() }
}
}