1use ruma::{
4 api::client::media::get_content_thumbnail::v3::Method,
5 events::{
6 room::{
7 message::{
8 AudioMessageEventContent, FileMessageEventContent, ImageMessageEventContent,
9 LocationMessageEventContent, VideoMessageEventContent,
10 },
11 MediaSource,
12 },
13 sticker::StickerEventContent,
14 },
15 MxcUri, UInt,
16};
17use serde::{Deserialize, Serialize};
18
19const UNIQUE_SEPARATOR: &str = "_";
20
21pub trait UniqueKey {
23 fn unique_key(&self) -> String;
26}
27
28#[derive(Clone, Debug, Serialize, Deserialize)]
30pub enum MediaFormat {
31 File,
33
34 Thumbnail(MediaThumbnailSettings),
36}
37
38impl UniqueKey for MediaFormat {
39 fn unique_key(&self) -> String {
40 match self {
41 Self::File => "file".into(),
42 Self::Thumbnail(settings) => settings.unique_key(),
43 }
44 }
45}
46
47#[derive(Clone, Debug, Serialize, Deserialize)]
49pub struct MediaThumbnailSettings {
50 pub method: Method,
52
53 pub width: UInt,
56
57 pub height: UInt,
60
61 pub animated: bool,
68}
69
70impl MediaThumbnailSettings {
71 pub fn with_method(method: Method, width: UInt, height: UInt) -> Self {
76 Self { method, width, height, animated: false }
77 }
78
79 pub fn new(width: UInt, height: UInt) -> Self {
84 Self { method: Method::Scale, width, height, animated: false }
85 }
86}
87
88impl UniqueKey for MediaThumbnailSettings {
89 fn unique_key(&self) -> String {
90 let mut key = format!("{}{UNIQUE_SEPARATOR}{}x{}", self.method, self.width, self.height);
91
92 if self.animated {
93 key.push_str(UNIQUE_SEPARATOR);
94 key.push_str("animated");
95 }
96
97 key
98 }
99}
100
101impl UniqueKey for MediaSource {
102 fn unique_key(&self) -> String {
103 match self {
104 Self::Plain(uri) => uri.to_string(),
105 Self::Encrypted(file) => file.url.to_string(),
106 }
107 }
108}
109
110#[derive(Clone, Debug, Serialize, Deserialize)]
114pub struct MediaRequestParameters {
115 pub source: MediaSource,
117
118 pub format: MediaFormat,
120}
121
122impl MediaRequestParameters {
123 pub fn uri(&self) -> &MxcUri {
125 match &self.source {
126 MediaSource::Plain(url) => url.as_ref(),
127 MediaSource::Encrypted(file) => file.url.as_ref(),
128 }
129 }
130}
131
132impl UniqueKey for MediaRequestParameters {
133 fn unique_key(&self) -> String {
134 format!("{}{UNIQUE_SEPARATOR}{}", self.source.unique_key(), self.format.unique_key())
135 }
136}
137
138pub trait MediaEventContent {
140 fn source(&self) -> Option<MediaSource>;
144
145 fn thumbnail_source(&self) -> Option<MediaSource>;
149}
150
151impl MediaEventContent for StickerEventContent {
152 fn source(&self) -> Option<MediaSource> {
153 Some(MediaSource::from(self.source.clone()))
154 }
155
156 fn thumbnail_source(&self) -> Option<MediaSource> {
157 None
158 }
159}
160
161impl MediaEventContent for AudioMessageEventContent {
162 fn source(&self) -> Option<MediaSource> {
163 Some(self.source.clone())
164 }
165
166 fn thumbnail_source(&self) -> Option<MediaSource> {
167 None
168 }
169}
170
171impl MediaEventContent for FileMessageEventContent {
172 fn source(&self) -> Option<MediaSource> {
173 Some(self.source.clone())
174 }
175
176 fn thumbnail_source(&self) -> Option<MediaSource> {
177 self.info.as_ref()?.thumbnail_source.clone()
178 }
179}
180
181impl MediaEventContent for ImageMessageEventContent {
182 fn source(&self) -> Option<MediaSource> {
183 Some(self.source.clone())
184 }
185
186 fn thumbnail_source(&self) -> Option<MediaSource> {
187 self.info
188 .as_ref()
189 .and_then(|info| info.thumbnail_source.clone())
190 .or_else(|| Some(self.source.clone()))
191 }
192}
193
194impl MediaEventContent for VideoMessageEventContent {
195 fn source(&self) -> Option<MediaSource> {
196 Some(self.source.clone())
197 }
198
199 fn thumbnail_source(&self) -> Option<MediaSource> {
200 self.info
201 .as_ref()
202 .and_then(|info| info.thumbnail_source.clone())
203 .or_else(|| Some(self.source.clone()))
204 }
205}
206
207impl MediaEventContent for LocationMessageEventContent {
208 fn source(&self) -> Option<MediaSource> {
209 None
210 }
211
212 fn thumbnail_source(&self) -> Option<MediaSource> {
213 self.info.as_ref()?.thumbnail_source.clone()
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use ruma::mxc_uri;
220 use serde_json::json;
221
222 use super::*;
223
224 #[test]
225 fn test_media_request_url() {
226 let mxc_uri = mxc_uri!("mxc://homeserver/media");
227
228 let plain = MediaRequestParameters {
229 source: MediaSource::Plain(mxc_uri.to_owned()),
230 format: MediaFormat::File,
231 };
232
233 assert_eq!(plain.uri(), mxc_uri);
234
235 let file = MediaRequestParameters {
236 source: MediaSource::Encrypted(Box::new(
237 serde_json::from_value(json!({
238 "url": mxc_uri,
239 "key": {
240 "kty": "oct",
241 "key_ops": ["encrypt", "decrypt"],
242 "alg": "A256CTR",
243 "k": "b50ACIv6LMn9AfMCFD1POJI_UAFWIclxAN1kWrEO2X8",
244 "ext": true,
245 },
246 "iv": "AK1wyzigZtQAAAABAAAAKK",
247 "hashes": {
248 "sha256": "foobar",
249 },
250 "v": "v2",
251 }))
252 .unwrap(),
253 )),
254 format: MediaFormat::File,
255 };
256
257 assert_eq!(file.uri(), mxc_uri);
258 }
259}