use ruma::{
api::client::media::get_content_thumbnail::v3::Method,
events::{
room::{
message::{
AudioMessageEventContent, FileMessageEventContent, ImageMessageEventContent,
LocationMessageEventContent, VideoMessageEventContent,
},
MediaSource,
},
sticker::StickerEventContent,
},
MxcUri, UInt,
};
use serde::{Deserialize, Serialize};
const UNIQUE_SEPARATOR: &str = "_";
pub trait UniqueKey {
fn unique_key(&self) -> String;
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum MediaFormat {
File,
Thumbnail(MediaThumbnailSettings),
}
impl UniqueKey for MediaFormat {
fn unique_key(&self) -> String {
match self {
Self::File => "file".into(),
Self::Thumbnail(settings) => settings.unique_key(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MediaThumbnailSettings {
pub method: Method,
pub width: UInt,
pub height: UInt,
pub animated: bool,
}
impl MediaThumbnailSettings {
pub fn with_method(method: Method, width: UInt, height: UInt) -> Self {
Self { method, width, height, animated: false }
}
pub fn new(width: UInt, height: UInt) -> Self {
Self { method: Method::Scale, width, height, animated: false }
}
}
impl UniqueKey for MediaThumbnailSettings {
fn unique_key(&self) -> String {
let mut key = format!("{}{UNIQUE_SEPARATOR}{}x{}", self.method, self.width, self.height);
if self.animated {
key.push_str(UNIQUE_SEPARATOR);
key.push_str("animated");
}
key
}
}
impl UniqueKey for MediaSource {
fn unique_key(&self) -> String {
match self {
Self::Plain(uri) => uri.to_string(),
Self::Encrypted(file) => file.url.to_string(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MediaRequestParameters {
pub source: MediaSource,
pub format: MediaFormat,
}
impl MediaRequestParameters {
pub fn uri(&self) -> &MxcUri {
match &self.source {
MediaSource::Plain(url) => url.as_ref(),
MediaSource::Encrypted(file) => file.url.as_ref(),
}
}
}
impl UniqueKey for MediaRequestParameters {
fn unique_key(&self) -> String {
format!("{}{UNIQUE_SEPARATOR}{}", self.source.unique_key(), self.format.unique_key())
}
}
pub trait MediaEventContent {
fn source(&self) -> Option<MediaSource>;
fn thumbnail_source(&self) -> Option<MediaSource>;
}
impl MediaEventContent for StickerEventContent {
fn source(&self) -> Option<MediaSource> {
Some(MediaSource::from(self.source.clone()))
}
fn thumbnail_source(&self) -> Option<MediaSource> {
None
}
}
impl MediaEventContent for AudioMessageEventContent {
fn source(&self) -> Option<MediaSource> {
Some(self.source.clone())
}
fn thumbnail_source(&self) -> Option<MediaSource> {
None
}
}
impl MediaEventContent for FileMessageEventContent {
fn source(&self) -> Option<MediaSource> {
Some(self.source.clone())
}
fn thumbnail_source(&self) -> Option<MediaSource> {
self.info.as_ref()?.thumbnail_source.clone()
}
}
impl MediaEventContent for ImageMessageEventContent {
fn source(&self) -> Option<MediaSource> {
Some(self.source.clone())
}
fn thumbnail_source(&self) -> Option<MediaSource> {
self.info
.as_ref()
.and_then(|info| info.thumbnail_source.clone())
.or_else(|| Some(self.source.clone()))
}
}
impl MediaEventContent for VideoMessageEventContent {
fn source(&self) -> Option<MediaSource> {
Some(self.source.clone())
}
fn thumbnail_source(&self) -> Option<MediaSource> {
self.info
.as_ref()
.and_then(|info| info.thumbnail_source.clone())
.or_else(|| Some(self.source.clone()))
}
}
impl MediaEventContent for LocationMessageEventContent {
fn source(&self) -> Option<MediaSource> {
None
}
fn thumbnail_source(&self) -> Option<MediaSource> {
self.info.as_ref()?.thumbnail_source.clone()
}
}
#[cfg(test)]
mod tests {
use ruma::mxc_uri;
use serde_json::json;
use super::*;
#[test]
fn test_media_request_url() {
let mxc_uri = mxc_uri!("mxc://homeserver/media");
let plain = MediaRequestParameters {
source: MediaSource::Plain(mxc_uri.to_owned()),
format: MediaFormat::File,
};
assert_eq!(plain.uri(), mxc_uri);
let file = MediaRequestParameters {
source: MediaSource::Encrypted(Box::new(
serde_json::from_value(json!({
"url": mxc_uri,
"key": {
"kty": "oct",
"key_ops": ["encrypt", "decrypt"],
"alg": "A256CTR",
"k": "b50ACIv6LMn9AfMCFD1POJI_UAFWIclxAN1kWrEO2X8",
"ext": true,
},
"iv": "AK1wyzigZtQAAAABAAAAKK",
"hashes": {
"sha256": "foobar",
},
"v": "v2",
}))
.unwrap(),
)),
format: MediaFormat::File,
};
assert_eq!(file.uri(), mxc_uri);
}
}