use std::ops::Deref;
#[cfg(feature = "unstable-msc1767")]
use ruma_macros::EventContent;
use serde::{Deserialize, Serialize};
#[cfg(feature = "unstable-msc1767")]
use super::room::message::Relation;
#[cfg(feature = "unstable-msc4095")]
use super::room::message::UrlPreview;
#[cfg(feature = "unstable-msc1767")]
pub(super) mod historical_serde;
#[cfg(feature = "unstable-msc1767")]
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
#[ruma_event(type = "org.matrix.msc1767.message", kind = MessageLike, without_relation)]
pub struct MessageEventContent {
#[serde(rename = "org.matrix.msc1767.text", alias = "m.text")]
pub text: TextContentBlock,
#[cfg(feature = "unstable-msc3955")]
#[serde(
default,
skip_serializing_if = "ruma_common::serde::is_default",
rename = "org.matrix.msc1767.automated"
)]
pub automated: bool,
#[serde(
flatten,
skip_serializing_if = "Option::is_none",
deserialize_with = "crate::room::message::relation_serde::deserialize_relation"
)]
pub relates_to: Option<Relation<MessageEventContentWithoutRelation>>,
#[cfg(feature = "unstable-msc4095")]
#[serde(
rename = "com.beeper.linkpreviews",
skip_serializing_if = "Option::is_none",
alias = "m.url_previews"
)]
pub url_previews: Option<Vec<UrlPreview>>,
}
#[cfg(feature = "unstable-msc1767")]
impl MessageEventContent {
pub fn plain(body: impl Into<String>) -> Self {
Self {
text: TextContentBlock::plain(body),
#[cfg(feature = "unstable-msc3955")]
automated: false,
relates_to: None,
#[cfg(feature = "unstable-msc4095")]
url_previews: None,
}
}
pub fn html(body: impl Into<String>, html_body: impl Into<String>) -> Self {
Self {
text: TextContentBlock::html(body, html_body),
#[cfg(feature = "unstable-msc3955")]
automated: false,
relates_to: None,
#[cfg(feature = "unstable-msc4095")]
url_previews: None,
}
}
#[cfg(feature = "markdown")]
pub fn markdown(body: impl AsRef<str> + Into<String>) -> Self {
Self {
text: TextContentBlock::markdown(body),
#[cfg(feature = "unstable-msc3955")]
automated: false,
relates_to: None,
#[cfg(feature = "unstable-msc4095")]
url_previews: None,
}
}
}
#[cfg(feature = "unstable-msc1767")]
impl From<TextContentBlock> for MessageEventContent {
fn from(text: TextContentBlock) -> Self {
Self {
text,
#[cfg(feature = "unstable-msc3955")]
automated: false,
relates_to: None,
#[cfg(feature = "unstable-msc4095")]
url_previews: None,
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct TextContentBlock(Vec<TextRepresentation>);
impl TextContentBlock {
pub fn plain(body: impl Into<String>) -> Self {
Self(vec![TextRepresentation::plain(body)])
}
pub fn html(body: impl Into<String>, html_body: impl Into<String>) -> Self {
Self(vec![TextRepresentation::html(html_body), TextRepresentation::plain(body)])
}
#[cfg(feature = "markdown")]
pub fn markdown(body: impl AsRef<str> + Into<String>) -> Self {
let mut message = Vec::with_capacity(2);
if let Some(html_body) = TextRepresentation::markdown(&body) {
message.push(html_body);
}
message.push(TextRepresentation::plain(body));
Self(message)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn find_plain(&self) -> Option<&str> {
self.iter()
.find(|content| content.mimetype == "text/plain")
.map(|content| content.body.as_ref())
}
pub fn find_html(&self) -> Option<&str> {
self.iter()
.find(|content| content.mimetype == "text/html")
.map(|content| content.body.as_ref())
}
}
impl From<Vec<TextRepresentation>> for TextContentBlock {
fn from(representations: Vec<TextRepresentation>) -> Self {
Self(representations)
}
}
impl FromIterator<TextRepresentation> for TextContentBlock {
fn from_iter<T: IntoIterator<Item = TextRepresentation>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
}
impl Deref for TextContentBlock {
type Target = [TextRepresentation];
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct TextRepresentation {
#[serde(
default = "TextRepresentation::default_mimetype",
skip_serializing_if = "TextRepresentation::is_default_mimetype"
)]
pub mimetype: String,
pub body: String,
#[cfg(feature = "unstable-msc3554")]
#[serde(
rename = "org.matrix.msc3554.lang",
default = "TextRepresentation::default_lang",
skip_serializing_if = "TextRepresentation::is_default_lang"
)]
pub lang: String,
}
impl TextRepresentation {
pub fn new(mimetype: impl Into<String>, body: impl Into<String>) -> Self {
Self {
mimetype: mimetype.into(),
body: body.into(),
#[cfg(feature = "unstable-msc3554")]
lang: Self::default_lang(),
}
}
pub fn plain(body: impl Into<String>) -> Self {
Self::new("text/plain", body)
}
pub fn html(body: impl Into<String>) -> Self {
Self::new("text/html", body)
}
#[cfg(feature = "markdown")]
pub fn markdown(body: impl AsRef<str>) -> Option<Self> {
use super::room::message::parse_markdown;
parse_markdown(body.as_ref()).map(Self::html)
}
fn default_mimetype() -> String {
"text/plain".to_owned()
}
fn is_default_mimetype(mime: &str) -> bool {
mime == "text/plain"
}
#[cfg(feature = "unstable-msc3554")]
fn default_lang() -> String {
"en".to_owned()
}
#[cfg(feature = "unstable-msc3554")]
fn is_default_lang(lang: &str) -> bool {
lang == "en"
}
}