Skip to main content

matrix_sdk/
attachment.rs

1// Copyright 2022 Kévin Commaille
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Types and traits for attachments.
16
17use std::time::Duration;
18
19use ruma::{
20    OwnedTransactionId, UInt, assign,
21    events::{
22        Mentions,
23        room::{
24            ImageInfo, ThumbnailInfo,
25            message::{AudioInfo, FileInfo, TextMessageEventContent, VideoInfo},
26        },
27    },
28};
29
30use crate::room::reply::Reply;
31
32/// Base metadata about an image.
33#[derive(Debug, Clone, Default)]
34pub struct BaseImageInfo {
35    /// The height of the image in pixels.
36    pub height: Option<UInt>,
37    /// The width of the image in pixels.
38    pub width: Option<UInt>,
39    /// The file size of the image in bytes.
40    pub size: Option<UInt>,
41    /// The [BlurHash](https://blurha.sh/) for this image.
42    pub blurhash: Option<String>,
43    /// Whether this image is animated.
44    pub is_animated: Option<bool>,
45}
46
47/// Base metadata about a video.
48#[derive(Debug, Clone, Default)]
49pub struct BaseVideoInfo {
50    /// The duration of the video.
51    pub duration: Option<Duration>,
52    /// The height of the video in pixels.
53    pub height: Option<UInt>,
54    /// The width of the video in pixels.
55    pub width: Option<UInt>,
56    /// The file size of the video in bytes.
57    pub size: Option<UInt>,
58    /// The [BlurHash](https://blurha.sh/) for this video.
59    pub blurhash: Option<String>,
60    /// Whether this video should be displayed as a circle video
61    /// ([MSC4546](https://github.com/matrix-org/matrix-spec-proposals/pull/4546)).
62    ///
63    /// Defaults to `false`.
64    #[cfg(feature = "unstable-msc4546")]
65    pub circle: bool,
66}
67
68/// Base metadata about an audio clip.
69#[derive(Debug, Clone, Default)]
70pub struct BaseAudioInfo {
71    /// The duration of the audio clip.
72    pub duration: Option<Duration>,
73    /// The file size of the audio clip in bytes.
74    pub size: Option<UInt>,
75    /// The waveform of the audio clip.
76    ///
77    /// Must only include values between 0 and 1.
78    pub waveform: Option<Vec<f32>>,
79}
80
81/// Base metadata about a file.
82#[derive(Debug, Clone, Default)]
83pub struct BaseFileInfo {
84    /// The size of the file in bytes.
85    pub size: Option<UInt>,
86}
87
88/// Types of metadata for an attachment.
89#[derive(Debug)]
90pub enum AttachmentInfo {
91    /// The metadata of an image.
92    Image(BaseImageInfo),
93    /// The metadata of a video.
94    Video(BaseVideoInfo),
95    /// The metadata of an audio clip.
96    Audio(BaseAudioInfo),
97    /// The metadata of a file.
98    File(BaseFileInfo),
99    /// The metadata of a voice message
100    Voice(BaseAudioInfo),
101}
102
103impl From<AttachmentInfo> for ImageInfo {
104    fn from(info: AttachmentInfo) -> Self {
105        match info {
106            AttachmentInfo::Image(info) => assign!(ImageInfo::new(), {
107                height: info.height,
108                width: info.width,
109                size: info.size,
110                blurhash: info.blurhash,
111                is_animated: info.is_animated,
112            }),
113            _ => ImageInfo::new(),
114        }
115    }
116}
117
118impl From<AttachmentInfo> for VideoInfo {
119    fn from(info: AttachmentInfo) -> Self {
120        match info {
121            AttachmentInfo::Video(info) => assign!(VideoInfo::new(), {
122                duration: info.duration,
123                height: info.height,
124                width: info.width,
125                size: info.size,
126                blurhash: info.blurhash,
127            }),
128            _ => VideoInfo::new(),
129        }
130    }
131}
132
133impl From<AttachmentInfo> for AudioInfo {
134    fn from(info: AttachmentInfo) -> Self {
135        match info {
136            AttachmentInfo::Audio(info) | AttachmentInfo::Voice(info) => {
137                assign!(AudioInfo::new(), {
138                    duration: info.duration,
139                    size: info.size,
140                })
141            }
142            _ => AudioInfo::new(),
143        }
144    }
145}
146
147impl From<AttachmentInfo> for FileInfo {
148    fn from(info: AttachmentInfo) -> Self {
149        match info {
150            AttachmentInfo::File(info) => assign!(FileInfo::new(), {
151                size: info.size,
152            }),
153            _ => FileInfo::new(),
154        }
155    }
156}
157
158/// A thumbnail to upload and send for an attachment.
159#[derive(Debug)]
160pub struct Thumbnail {
161    /// The raw bytes of the thumbnail.
162    pub data: Vec<u8>,
163    /// The type of the thumbnail, this will be used as the content-type header.
164    pub content_type: mime::Mime,
165    /// The height of the thumbnail in pixels.
166    pub height: UInt,
167    /// The width of the thumbnail in pixels.
168    pub width: UInt,
169    /// The file size of the thumbnail in bytes.
170    pub size: UInt,
171}
172
173impl Thumbnail {
174    /// Convert this `Thumbnail` into a `(data, content_type, info)` tuple.
175    pub fn into_parts(self) -> (Vec<u8>, mime::Mime, Box<ThumbnailInfo>) {
176        let thumbnail_info = assign!(ThumbnailInfo::new(), {
177            height: Some(self.height),
178            width: Some(self.width),
179            size: Some(self.size),
180            mimetype: Some(self.content_type.to_string())
181        });
182        (self.data, self.content_type, Box::new(thumbnail_info))
183    }
184}
185
186/// Configuration for sending an attachment.
187#[derive(Debug, Default)]
188pub struct AttachmentConfig {
189    /// A fixed transaction id to be used for sending this attachment.
190    ///
191    /// Otherwise, a random one will be generated.
192    pub txn_id: Option<OwnedTransactionId>,
193
194    /// Type-specific metadata about the attachment.
195    pub info: Option<AttachmentInfo>,
196
197    /// An optional thumbnail to send with the attachment.
198    pub thumbnail: Option<Thumbnail>,
199
200    /// An optional caption for the attachment.
201    pub caption: Option<TextMessageEventContent>,
202
203    /// Intentional mentions to be included in the media event.
204    pub mentions: Option<Mentions>,
205
206    /// Reply parameters for the attachment (replied-to event and thread-related
207    /// metadata).
208    pub reply: Option<Reply>,
209
210    /// Additional fields to include in the media event's content. Objects such
211    /// as `info` are merged recursively. The event's own fields take precedence
212    /// on conflicts.
213    pub extra_content: Option<serde_json::Map<String, serde_json::Value>>,
214}
215
216impl AttachmentConfig {
217    /// Create a new empty `AttachmentConfig`.
218    pub fn new() -> Self {
219        Self::default()
220    }
221
222    /// Set the thumbnail to send.
223    ///
224    /// # Arguments
225    ///
226    /// - `thumbnail` - The thumbnail of the media. If the `content_type` does
227    ///   not support it (e.g. audio clips), it is ignored.
228    #[must_use]
229    pub fn thumbnail(mut self, thumbnail: Option<Thumbnail>) -> Self {
230        self.thumbnail = thumbnail;
231        self
232    }
233
234    /// Set the transaction ID to send.
235    ///
236    /// # Arguments
237    ///
238    /// - `txn_id` - A unique ID that can be attached to a `MessageEvent` held
239    ///   in its unsigned field as `transaction_id`. If not given, one is
240    ///   created for the message.
241    #[must_use]
242    pub fn txn_id(mut self, txn_id: OwnedTransactionId) -> Self {
243        self.txn_id = Some(txn_id);
244        self
245    }
246
247    /// Set the media metadata to send.
248    ///
249    /// # Arguments
250    ///
251    /// - `info` - The metadata of the media. If the `AttachmentInfo` type
252    ///   doesn't match the `content_type`, it is ignored.
253    #[must_use]
254    pub fn info(mut self, info: AttachmentInfo) -> Self {
255        self.info = Some(info);
256        self
257    }
258
259    /// Set the optional caption.
260    ///
261    /// # Arguments
262    ///
263    /// * `caption` - The optional caption.
264    pub fn caption(mut self, caption: Option<TextMessageEventContent>) -> Self {
265        self.caption = caption;
266        self
267    }
268
269    /// Set the mentions of the message.
270    ///
271    /// # Arguments
272    ///
273    /// * `mentions` - The mentions of the message.
274    pub fn mentions(mut self, mentions: Option<Mentions>) -> Self {
275        self.mentions = mentions;
276        self
277    }
278
279    /// Set the reply information of the message.
280    ///
281    /// # Arguments
282    ///
283    /// * `reply` - The reply information of the message.
284    pub fn reply(mut self, reply: Option<Reply>) -> Self {
285        self.reply = reply;
286        self
287    }
288
289    /// Set additional fields for the media event's content.
290    ///
291    /// # Arguments
292    ///
293    /// * `extra_content` - The additional fields.
294    pub fn extra_content(
295        mut self,
296        extra_content: Option<serde_json::Map<String, serde_json::Value>>,
297    ) -> Self {
298        self.extra_content = extra_content;
299        self
300    }
301}
302
303/// Configuration for sending a gallery.
304#[cfg(feature = "unstable-msc4274")]
305#[derive(Debug, Default)]
306pub struct GalleryConfig {
307    pub(crate) txn_id: Option<OwnedTransactionId>,
308    pub(crate) items: Vec<GalleryItemInfo>,
309    pub(crate) caption: Option<TextMessageEventContent>,
310    pub(crate) mentions: Option<Mentions>,
311    pub(crate) reply: Option<Reply>,
312    pub(crate) extra_content: Option<serde_json::Map<String, serde_json::Value>>,
313}
314
315#[cfg(feature = "unstable-msc4274")]
316impl GalleryConfig {
317    /// Create a new empty `GalleryConfig`.
318    pub fn new() -> Self {
319        Self::default()
320    }
321
322    /// Set the transaction ID to send.
323    ///
324    /// # Arguments
325    ///
326    /// - `txn_id` - A unique ID that can be attached to a `MessageEvent` held
327    ///   in its unsigned field as `transaction_id`. If not given, one is
328    ///   created for the message.
329    #[must_use]
330    pub fn txn_id(mut self, txn_id: OwnedTransactionId) -> Self {
331        self.txn_id = Some(txn_id);
332        self
333    }
334
335    /// Adds a media item to the gallery.
336    ///
337    /// # Arguments
338    ///
339    /// * `item` - Information about the item to be added.
340    #[must_use]
341    pub fn add_item(mut self, item: GalleryItemInfo) -> Self {
342        self.items.push(item);
343        self
344    }
345
346    /// Set the optional caption.
347    ///
348    /// # Arguments
349    ///
350    /// * `caption` - The optional caption.
351    pub fn caption(mut self, caption: Option<TextMessageEventContent>) -> Self {
352        self.caption = caption;
353        self
354    }
355
356    /// Set the mentions of the message.
357    ///
358    /// # Arguments
359    ///
360    /// * `mentions` - The mentions of the message.
361    pub fn mentions(mut self, mentions: Option<Mentions>) -> Self {
362        self.mentions = mentions;
363        self
364    }
365
366    /// Set the reply information of the message.
367    ///
368    /// # Arguments
369    ///
370    /// * `reply` - The reply information of the message.
371    pub fn reply(mut self, reply: Option<Reply>) -> Self {
372        self.reply = reply;
373        self
374    }
375
376    /// Set additional top-level fields for the gallery event's content.
377    ///
378    /// Objects are merged recursively; the event's own fields take precedence
379    /// on conflicts. To add fields to individual items, use
380    /// [`GalleryItemInfo::extra_content`].
381    #[must_use]
382    pub fn extra_content(
383        mut self,
384        extra_content: Option<serde_json::Map<String, serde_json::Value>>,
385    ) -> Self {
386        self.extra_content = extra_content;
387        self
388    }
389
390    /// Returns the number of media items in the gallery.
391    pub fn len(&self) -> usize {
392        self.items.len()
393    }
394
395    /// Checks whether the gallery contains any media items or not.
396    pub fn is_empty(&self) -> bool {
397        self.items.is_empty()
398    }
399}
400
401#[cfg(feature = "unstable-msc4274")]
402#[derive(Debug)]
403/// Metadata for a gallery item
404pub struct GalleryItemInfo {
405    /// The filename.
406    pub filename: String,
407    /// The mime type.
408    pub content_type: mime::Mime,
409    /// The binary data.
410    pub data: Vec<u8>,
411    /// The attachment info.
412    pub attachment_info: AttachmentInfo,
413    /// The caption.
414    pub caption: Option<TextMessageEventContent>,
415    /// The thumbnail.
416    pub thumbnail: Option<Thumbnail>,
417    /// Additional fields to merge into this item's content, for example a
418    /// spoiler flag. Objects are merged recursively; the item's own fields take
419    /// precedence on conflicts.
420    pub extra_content: Option<serde_json::Map<String, serde_json::Value>>,
421}