From 10069fbead08b996b4850156b13ffc76bc8f5c1e Mon Sep 17 00:00:00 2001 From: Marco Antonio Alvarez Date: Tue, 19 Mar 2024 11:08:47 +0100 Subject: [PATCH] MSC2530: added the ability to send media with captions (#3226) Now that there is some support for [MSC2530](https://github.com/matrix-org/matrix-spec-proposals/pull/2530), I gave adding sending captions a try. ( This is my first time with Rust :smile: ) I tried it on Element X with a hardcoded caption and it seems to work well ![image](https://github.com/matrix-org/matrix-rust-sdk/assets/683652/597e5ebf-f7f2-498f-97a4-ac98613c1134) (It even got forwarded through mautrix-whatsapp and the caption was visible on the Whatsapp side) --- * ffi: Expose filename and formatted body fields for media captions In relevance to MSC2530 * MSC2530: added the ability to send media with captions Signed-off-by: Marco Antonio Alvarez * signoff Signed-off-by: Marco Antonio Alvarez * fixing the import messup * fix missing parameters in documentation * fix formatting * move optional parameters to the end * more formatting fixes * more formatting fixes * rename url parameter to filename in send_attachment and helpers * fix send_attachment documentation example * move caption and formatted_caption into attachmentconfig * fix formatting * fix formatting * fix formatting (hopefully the last one) * updated stale comments * simplify attachment message comments --------- Signed-off-by: Marco Antonio Alvarez Co-authored-by: SpiritCroc --- bindings/matrix-sdk-ffi/src/timeline/mod.rs | 38 +++++++++++---- crates/matrix-sdk-ui/src/timeline/futures.rs | 12 ++--- crates/matrix-sdk-ui/src/timeline/mod.rs | 7 +-- crates/matrix-sdk/src/attachment.rs | 30 +++++++++++- crates/matrix-sdk/src/encryption/mod.rs | 35 +++++++++----- crates/matrix-sdk/src/media.rs | 46 +++++++++++++------ crates/matrix-sdk/src/room/futures.rs | 14 +++--- crates/matrix-sdk/src/room/mod.rs | 40 +++++----------- .../tests/integration/room/joined.rs | 34 ++++++++------ examples/image_bot/src/main.rs | 11 +++-- 10 files changed, 169 insertions(+), 98 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/timeline/mod.rs b/bindings/matrix-sdk-ffi/src/timeline/mod.rs index a118b290ba7..48859c77c2e 100644 --- a/bindings/matrix-sdk-ffi/src/timeline/mod.rs +++ b/bindings/matrix-sdk-ffi/src/timeline/mod.rs @@ -52,19 +52,21 @@ use tokio::{ use tracing::{error, info, warn}; use uuid::Uuid; +use self::content::{Reaction, ReactionSenderData, TimelineItemContent}; use crate::{ client::ProgressWatcher, error::{ClientError, RoomError}, helpers::unwrap_or_clone_arc, - ruma::{AssetType, AudioInfo, FileInfo, ImageInfo, PollKind, ThumbnailInfo, VideoInfo}, + ruma::{ + AssetType, AudioInfo, FileInfo, FormattedBody, ImageInfo, PollKind, ThumbnailInfo, + VideoInfo, + }, task_handle::TaskHandle, RUNTIME, }; mod content; -pub use self::content::{Reaction, ReactionSenderData, TimelineItemContent}; - #[derive(uniffi::Object)] #[repr(transparent)] pub struct Timeline { @@ -106,12 +108,12 @@ impl Timeline { async fn send_attachment( &self, - url: String, + filename: String, mime_type: Mime, attachment_config: AttachmentConfig, progress_watcher: Option>, ) -> Result<(), RoomError> { - let request = self.inner.send_attachment(url, mime_type, attachment_config); + let request = self.inner.send_attachment(filename, mime_type, attachment_config); if let Some(progress_watcher) = progress_watcher { let mut subscriber = request.subscribe_to_send_progress(); RUNTIME.spawn(async move { @@ -218,6 +220,8 @@ impl Timeline { url: String, thumbnail_url: Option, image_info: ImageInfo, + caption: Option, + formatted_caption: Option, progress_watcher: Option>, ) -> Arc { SendAttachmentJoinHandle::new(RUNTIME.spawn(async move { @@ -238,7 +242,9 @@ impl Timeline { AttachmentConfig::with_thumbnail(thumbnail).info(attachment_info) } _ => AttachmentConfig::new().info(attachment_info), - }; + } + .caption(caption) + .formatted_caption(formatted_caption.map(Into::into)); self.send_attachment(url, mime_type, attachment_config, progress_watcher).await })) @@ -249,6 +255,8 @@ impl Timeline { url: String, thumbnail_url: Option, video_info: VideoInfo, + caption: Option, + formatted_caption: Option, progress_watcher: Option>, ) -> Arc { SendAttachmentJoinHandle::new(RUNTIME.spawn(async move { @@ -269,7 +277,9 @@ impl Timeline { AttachmentConfig::with_thumbnail(thumbnail).info(attachment_info) } _ => AttachmentConfig::new().info(attachment_info), - }; + } + .caption(caption) + .formatted_caption(formatted_caption.map(Into::into)); self.send_attachment(url, mime_type, attachment_config, progress_watcher).await })) @@ -279,6 +289,8 @@ impl Timeline { self: Arc, url: String, audio_info: AudioInfo, + caption: Option, + formatted_caption: Option, progress_watcher: Option>, ) -> Arc { SendAttachmentJoinHandle::new(RUNTIME.spawn(async move { @@ -291,7 +303,10 @@ impl Timeline { .map_err(|_| RoomError::InvalidAttachmentData)?; let attachment_info = AttachmentInfo::Audio(base_audio_info); - let attachment_config = AttachmentConfig::new().info(attachment_info); + let attachment_config = AttachmentConfig::new() + .info(attachment_info) + .caption(caption) + .formatted_caption(formatted_caption.map(Into::into)); self.send_attachment(url, mime_type, attachment_config, progress_watcher).await })) @@ -302,6 +317,8 @@ impl Timeline { url: String, audio_info: AudioInfo, waveform: Vec, + caption: Option, + formatted_caption: Option, progress_watcher: Option>, ) -> Arc { SendAttachmentJoinHandle::new(RUNTIME.spawn(async move { @@ -315,7 +332,10 @@ impl Timeline { let attachment_info = AttachmentInfo::Voice { audio_info: base_audio_info, waveform: Some(waveform) }; - let attachment_config = AttachmentConfig::new().info(attachment_info); + let attachment_config = AttachmentConfig::new() + .info(attachment_info) + .caption(caption) + .formatted_caption(formatted_caption.map(Into::into)); self.send_attachment(url, mime_type, attachment_config, progress_watcher).await })) diff --git a/crates/matrix-sdk-ui/src/timeline/futures.rs b/crates/matrix-sdk-ui/src/timeline/futures.rs index d7d9e1bd36b..459be113de1 100644 --- a/crates/matrix-sdk-ui/src/timeline/futures.rs +++ b/crates/matrix-sdk-ui/src/timeline/futures.rs @@ -10,7 +10,7 @@ use super::{Error, Timeline}; pub struct SendAttachment<'a> { timeline: &'a Timeline, - url: String, + filename: String, mime_type: Mime, config: AttachmentConfig, tracing_span: Span, @@ -20,13 +20,13 @@ pub struct SendAttachment<'a> { impl<'a> SendAttachment<'a> { pub(crate) fn new( timeline: &'a Timeline, - url: String, + filename: String, mime_type: Mime, config: AttachmentConfig, ) -> Self { Self { timeline, - url, + filename, mime_type, config, tracing_span: Span::current(), @@ -47,14 +47,14 @@ impl<'a> IntoFuture for SendAttachment<'a> { boxed_into_future!(extra_bounds: 'a); fn into_future(self) -> Self::IntoFuture { - let Self { timeline, url, mime_type, config, tracing_span, send_progress } = self; + let Self { timeline, filename, mime_type, config, tracing_span, send_progress } = self; let fut = async move { - let body = Path::new(&url) + let body = Path::new(&filename) .file_name() .ok_or(Error::InvalidAttachmentFileName)? .to_str() .expect("path was created from UTF-8 string, hence filename part is UTF-8 too"); - let data = fs::read(&url).map_err(|_| Error::InvalidAttachmentData)?; + let data = fs::read(&filename).map_err(|_| Error::InvalidAttachmentData)?; timeline .room() diff --git a/crates/matrix-sdk-ui/src/timeline/mod.rs b/crates/matrix-sdk-ui/src/timeline/mod.rs index 85a50022a18..a56a51fdccb 100644 --- a/crates/matrix-sdk-ui/src/timeline/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/mod.rs @@ -513,21 +513,22 @@ impl Timeline { /// /// # Arguments /// - /// * `url` - The url for the file to be sent + /// * `filename` - The filename of the file to be sent /// /// * `mime_type` - The attachment's mime type /// /// * `config` - An attachment configuration object containing details about /// the attachment + /// /// like a thumbnail, its size, duration etc. #[instrument(skip_all)] pub fn send_attachment( &self, - url: String, + filename: String, mime_type: Mime, config: AttachmentConfig, ) -> SendAttachment<'_> { - SendAttachment::new(self, url, mime_type, config) + SendAttachment::new(self, filename, mime_type, config) } /// Retry sending a message that previously failed to send. diff --git a/crates/matrix-sdk/src/attachment.rs b/crates/matrix-sdk/src/attachment.rs index 9a3b1b59b5d..a8417a67555 100644 --- a/crates/matrix-sdk/src/attachment.rs +++ b/crates/matrix-sdk/src/attachment.rs @@ -23,7 +23,7 @@ use image::GenericImageView; use ruma::{ assign, events::room::{ - message::{AudioInfo, FileInfo, VideoInfo}, + message::{AudioInfo, FileInfo, FormattedBody, VideoInfo}, ImageInfo, ThumbnailInfo, }, OwnedTransactionId, TransactionId, UInt, @@ -190,6 +190,8 @@ pub struct AttachmentConfig { pub(crate) txn_id: Option, pub(crate) info: Option, pub(crate) thumbnail: Option, + pub(crate) caption: Option, + pub(crate) formatted_caption: Option, #[cfg(feature = "image-proc")] pub(crate) generate_thumbnail: bool, #[cfg(feature = "image-proc")] @@ -205,6 +207,8 @@ impl AttachmentConfig { txn_id: Default::default(), info: Default::default(), thumbnail: None, + caption: None, + formatted_caption: None, #[cfg(feature = "image-proc")] generate_thumbnail: Default::default(), #[cfg(feature = "image-proc")] @@ -247,6 +251,8 @@ impl AttachmentConfig { txn_id: Default::default(), info: Default::default(), thumbnail: Some(thumbnail), + caption: None, + formatted_caption: None, #[cfg(feature = "image-proc")] generate_thumbnail: Default::default(), #[cfg(feature = "image-proc")] @@ -278,6 +284,26 @@ impl AttachmentConfig { self.info = Some(info); self } + + /// Set the optional caption + /// + /// # Arguments + /// + /// * `caption` - The optional caption + pub fn caption(mut self, caption: Option) -> Self { + self.caption = caption; + self + } + + /// Set the optional formatted caption + /// + /// # Arguments + /// + /// * `formatted_caption` - The optional formatted caption + pub fn formatted_caption(mut self, formatted_caption: Option) -> Self { + self.formatted_caption = formatted_caption; + self + } } impl Default for AttachmentConfig { @@ -331,7 +357,7 @@ impl Default for AttachmentConfig { /// /// if let Some(room) = client.get_room(&room_id) { /// room.send_attachment( -/// "My favorite cat", +/// "my_favorite_cat.jpg", /// &mime::IMAGE_JPEG, /// image, /// config, diff --git a/crates/matrix-sdk/src/encryption/mod.rs b/crates/matrix-sdk/src/encryption/mod.rs index 5ce64635908..de5774c1ac9 100644 --- a/crates/matrix-sdk/src/encryption/mod.rs +++ b/crates/matrix-sdk/src/encryption/mod.rs @@ -68,7 +68,7 @@ use self::{ tasks::{BackupDownloadTask, BackupUploadingTask, ClientTasks}, }; use crate::{ - attachment::{AttachmentInfo, Thumbnail}, + attachment::{AttachmentConfig, Thumbnail}, client::ClientInner, encryption::{ identities::{Device, UserDevices}, @@ -298,18 +298,17 @@ impl Client { } /// Encrypt and upload the file to be read from `reader` and construct an - /// attachment message with `body`, `content_type`, `info` and `thumbnail`. + /// attachment message. pub(crate) async fn prepare_encrypted_attachment_message( &self, - body: &str, + filename: &str, content_type: &mime::Mime, data: Vec, - info: Option, - thumbnail: Option, + config: AttachmentConfig, send_progress: SharedObservable, ) -> Result { let upload_thumbnail = - self.upload_encrypted_thumbnail(thumbnail, content_type, send_progress.clone()); + self.upload_encrypted_thumbnail(config.thumbnail, content_type, send_progress.clone()); let upload_attachment = async { let mut cursor = Cursor::new(data); @@ -321,15 +320,25 @@ impl Client { let ((thumbnail_source, thumbnail_info), file) = try_join(upload_thumbnail, upload_attachment).await?; + // if config.caption is set, use it as body, and filename as the file name + // otherwise, body is the filename, and the filename is not set + // https://github.com/tulir/matrix-spec-proposals/blob/body-as-caption/proposals/2530-body-as-caption.md + let (body, filename) = match config.caption { + Some(caption) => (caption, Some(filename.to_owned())), + None => (filename.to_owned(), None), + }; + Ok(match content_type.type_() { mime::IMAGE => { - let info = assign!(info.map(ImageInfo::from).unwrap_or_default(), { + let info = assign!(config.info.map(ImageInfo::from).unwrap_or_default(), { mimetype: Some(content_type.as_ref().to_owned()), thumbnail_source, thumbnail_info }); let content = assign!(ImageMessageEventContent::encrypted(body.to_owned(), file), { - info: Some(Box::new(info)) + info: Some(Box::new(info)), + formatted: config.formatted_caption, + filename }); MessageType::Image(content) } @@ -339,22 +348,24 @@ impl Client { MessageType::Audio(crate::media::update_audio_message_event( audio_message_event_content, content_type, - info, + config.info, )) } mime::VIDEO => { - let info = assign!(info.map(VideoInfo::from).unwrap_or_default(), { + let info = assign!(config.info.map(VideoInfo::from).unwrap_or_default(), { mimetype: Some(content_type.as_ref().to_owned()), thumbnail_source, thumbnail_info }); let content = assign!(VideoMessageEventContent::encrypted(body.to_owned(), file), { - info: Some(Box::new(info)) + info: Some(Box::new(info)), + formatted: config.formatted_caption, + filename }); MessageType::Video(content) } _ => { - let info = assign!(info.map(FileInfo::from).unwrap_or_default(), { + let info = assign!(config.info.map(FileInfo::from).unwrap_or_default(), { mimetype: Some(content_type.as_ref().to_owned()), thumbnail_source, thumbnail_info diff --git a/crates/matrix-sdk/src/media.rs b/crates/matrix-sdk/src/media.rs index 8516d3d953c..708bbbf99f0 100644 --- a/crates/matrix-sdk/src/media.rs +++ b/crates/matrix-sdk/src/media.rs @@ -44,7 +44,7 @@ use tempfile::{Builder as TempFileBuilder, NamedTempFile, TempDir}; use tokio::{fs::File as TokioFile, io::AsyncWriteExt}; use crate::{ - attachment::{AttachmentInfo, Thumbnail}, + attachment::{AttachmentConfig, AttachmentInfo, Thumbnail}, futures::SendRequest, Client, Result, TransmissionProgress, }; @@ -437,17 +437,16 @@ impl Media { } /// Upload the file bytes in `data` and construct an attachment - /// message with `body`, `content_type`, `info` and `thumbnail`. + /// message. pub(crate) async fn prepare_attachment_message( &self, - body: &str, + filename: &str, content_type: &Mime, data: Vec, - info: Option, - thumbnail: Option, + config: AttachmentConfig, send_progress: SharedObservable, ) -> Result { - let upload_thumbnail = self.upload_thumbnail(thumbnail, send_progress.clone()); + let upload_thumbnail = self.upload_thumbnail(config.thumbnail, send_progress.clone()); let upload_attachment = async move { self.upload(content_type, data) @@ -459,46 +458,65 @@ impl Media { let ((thumbnail_source, thumbnail_info), response) = try_join(upload_thumbnail, upload_attachment).await?; + // if config.caption is set, use it as body, and filename as the file name + // otherwise, body is the filename, and the filename is not set + // https://github.com/tulir/matrix-spec-proposals/blob/body-as-caption/proposals/2530-body-as-caption.md + let (body, filename) = match config.caption { + Some(caption) => (caption, Some(filename.to_owned())), + None => (filename.to_owned(), None), + }; + let url = response.content_uri; Ok(match content_type.type_() { mime::IMAGE => { - let info = assign!(info.map(ImageInfo::from).unwrap_or_default(), { + let info = assign!(config.info.map(ImageInfo::from).unwrap_or_default(), { mimetype: Some(content_type.as_ref().to_owned()), thumbnail_source, thumbnail_info, }); MessageType::Image( - ImageMessageEventContent::plain(body.to_owned(), url).info(Box::new(info)), + ImageMessageEventContent::plain(body.to_owned(), url) + .info(Box::new(info)) + .filename(filename) + .formatted(config.formatted_caption), ) } mime::AUDIO => { let audio_message_event_content = - message::AudioMessageEventContent::plain(body.to_owned(), url); + message::AudioMessageEventContent::plain(body.to_owned(), url) + .filename(filename) + .formatted(config.formatted_caption); MessageType::Audio(update_audio_message_event( audio_message_event_content, content_type, - info, + config.info, )) } mime::VIDEO => { - let info = assign!(info.map(VideoInfo::from).unwrap_or_default(), { + let info = assign!(config.info.map(VideoInfo::from).unwrap_or_default(), { mimetype: Some(content_type.as_ref().to_owned()), thumbnail_source, thumbnail_info }); MessageType::Video( - VideoMessageEventContent::plain(body.to_owned(), url).info(Box::new(info)), + VideoMessageEventContent::plain(body.to_owned(), url) + .info(Box::new(info)) + .filename(filename) + .formatted(config.formatted_caption), ) } _ => { - let info = assign!(info.map(FileInfo::from).unwrap_or_default(), { + let info = assign!(config.info.map(FileInfo::from).unwrap_or_default(), { mimetype: Some(content_type.as_ref().to_owned()), thumbnail_source, thumbnail_info }); MessageType::File( - FileMessageEventContent::plain(body.to_owned(), url).info(Box::new(info)), + FileMessageEventContent::plain(body.to_owned(), url) + .info(Box::new(info)) + .filename(filename) + .formatted(config.formatted_caption), ) } }) diff --git a/crates/matrix-sdk/src/room/futures.rs b/crates/matrix-sdk/src/room/futures.rs index b599df1d5bf..4c3a749d0ca 100644 --- a/crates/matrix-sdk/src/room/futures.rs +++ b/crates/matrix-sdk/src/room/futures.rs @@ -216,7 +216,7 @@ impl<'a> IntoFuture for SendRawMessageLikeEvent<'a> { #[allow(missing_debug_implementations)] pub struct SendAttachment<'a> { room: &'a Room, - body: &'a str, + url: &'a str, content_type: &'a Mime, data: Vec, config: AttachmentConfig, @@ -227,14 +227,14 @@ pub struct SendAttachment<'a> { impl<'a> SendAttachment<'a> { pub(crate) fn new( room: &'a Room, - body: &'a str, + url: &'a str, content_type: &'a Mime, data: Vec, config: AttachmentConfig, ) -> Self { Self { room, - body, + url, content_type, data, config, @@ -260,10 +260,10 @@ impl<'a> IntoFuture for SendAttachment<'a> { boxed_into_future!(extra_bounds: 'a); fn into_future(self) -> Self::IntoFuture { - let Self { room, body, content_type, data, config, tracing_span, send_progress } = self; + let Self { room, url, content_type, data, config, tracing_span, send_progress } = self; let fut = async move { if config.thumbnail.is_some() { - room.prepare_and_send_attachment(body, content_type, data, config, send_progress) + room.prepare_and_send_attachment(url, content_type, data, config, send_progress) .await } else { #[cfg(not(feature = "image-proc"))] @@ -316,13 +316,15 @@ impl<'a> IntoFuture for SendAttachment<'a> { txn_id: config.txn_id, info: config.info, thumbnail, + caption: config.caption, + formatted_caption: config.formatted_caption, #[cfg(feature = "image-proc")] generate_thumbnail: false, #[cfg(feature = "image-proc")] thumbnail_size: None, }; - room.prepare_and_send_attachment(body, content_type, data, config, send_progress) + room.prepare_and_send_attachment(url, content_type, data, config, send_progress) .await } }; diff --git a/crates/matrix-sdk/src/room/mod.rs b/crates/matrix-sdk/src/room/mod.rs index d3bdafe93d3..af4d4e1784b 100644 --- a/crates/matrix-sdk/src/room/mod.rs +++ b/crates/matrix-sdk/src/room/mod.rs @@ -1692,8 +1692,7 @@ impl Room { /// [`upload()`] and afterwards the [`send()`]. /// /// # Arguments - /// * `body` - A textual representation of the media that is going to be - /// uploaded. Usually the file name. + /// * `filename` - The file name. /// /// * `content_type` - The type of the media, this will be used as the /// content-type header. @@ -1718,7 +1717,7 @@ impl Room { /// /// if let Some(room) = client.get_room(&room_id) { /// room.send_attachment( - /// "My favorite cat", + /// "my_favorite_cat.jpg", /// &mime::IMAGE_JPEG, /// image, /// AttachmentConfig::new(), @@ -1732,12 +1731,12 @@ impl Room { #[instrument(skip_all)] pub fn send_attachment<'a>( &'a self, - body: &'a str, + filename: &'a str, content_type: &'a Mime, data: Vec, config: AttachmentConfig, ) -> SendAttachment<'a> { - SendAttachment::new(self, body, content_type, data, config) + SendAttachment::new(self, filename, content_type, data, config) } /// Prepare and send an attachment to this room. @@ -1752,8 +1751,7 @@ impl Room { /// [`send()`](#method.send). /// /// # Arguments - /// * `body` - A textual representation of the media that is going to be - /// uploaded. Usually the file name. + /// * `filename` - The file name. /// /// * `content_type` - The type of the media, this will be used as the /// content-type header. @@ -1764,7 +1762,7 @@ impl Room { /// * `config` - Metadata and configuration for the attachment. pub(super) async fn prepare_and_send_attachment<'a>( &'a self, - body: &'a str, + filename: &'a str, content_type: &'a Mime, data: Vec, config: AttachmentConfig, @@ -1772,29 +1770,22 @@ impl Room { ) -> Result { self.ensure_room_joined()?; + let txn_id = config.txn_id.clone(); #[cfg(feature = "e2e-encryption")] let content = if self.is_encrypted().await? { self.client .prepare_encrypted_attachment_message( - body, + filename, content_type, data, - config.info, - config.thumbnail, + config, send_progress, ) .await? } else { self.client .media() - .prepare_attachment_message( - body, - content_type, - data, - config.info, - config.thumbnail, - send_progress, - ) + .prepare_attachment_message(filename, content_type, data, config, send_progress) .await? }; @@ -1802,18 +1793,11 @@ impl Room { let content = self .client .media() - .prepare_attachment_message( - body, - content_type, - data, - config.info, - config.thumbnail, - send_progress, - ) + .prepare_attachment_message(filename, content_type, data, config, send_progress) .await?; let mut fut = self.send(RoomMessageEventContent::new(content)); - if let Some(txn_id) = &config.txn_id { + if let Some(txn_id) = &txn_id { fut = fut.with_transaction_id(txn_id); } fut.await diff --git a/crates/matrix-sdk/tests/integration/room/joined.rs b/crates/matrix-sdk/tests/integration/room/joined.rs index 442ee5b67f1..8c753fcdb3a 100644 --- a/crates/matrix-sdk/tests/integration/room/joined.rs +++ b/crates/matrix-sdk/tests/integration/room/joined.rs @@ -418,15 +418,17 @@ async fn room_attachment_send_info() { let room = client.get_room(&DEFAULT_TEST_ROOM_ID).unwrap(); - let config = AttachmentConfig::new().info(AttachmentInfo::Image(BaseImageInfo { - height: Some(uint!(600)), - width: Some(uint!(800)), - size: None, - blurhash: None, - })); + let config = AttachmentConfig::new() + .info(AttachmentInfo::Image(BaseImageInfo { + height: Some(uint!(600)), + width: Some(uint!(800)), + size: None, + blurhash: None, + })) + .caption(Some("image caption".to_owned())); let response = room - .send_attachment("image", &mime::IMAGE_JPEG, b"Hello world".to_vec(), config) + .send_attachment("image.jpg", &mime::IMAGE_JPEG, b"Hello world".to_vec(), config) .await .unwrap(); @@ -470,16 +472,18 @@ async fn room_attachment_send_wrong_info() { let room = client.get_room(&DEFAULT_TEST_ROOM_ID).unwrap(); - let config = AttachmentConfig::new().info(AttachmentInfo::Video(BaseVideoInfo { - height: Some(uint!(600)), - width: Some(uint!(800)), - duration: Some(Duration::from_millis(3600)), - size: None, - blurhash: None, - })); + let config = AttachmentConfig::new() + .info(AttachmentInfo::Video(BaseVideoInfo { + height: Some(uint!(600)), + width: Some(uint!(800)), + duration: Some(Duration::from_millis(3600)), + size: None, + blurhash: None, + })) + .caption(Some("image caption".to_owned())); let response = - room.send_attachment("image", &mime::IMAGE_JPEG, b"Hello world".to_vec(), config).await; + room.send_attachment("image.jpg", &mime::IMAGE_JPEG, b"Hello world".to_vec(), config).await; response.unwrap_err(); } diff --git a/examples/image_bot/src/main.rs b/examples/image_bot/src/main.rs index 10e5515a919..d5f7a07c0f5 100644 --- a/examples/image_bot/src/main.rs +++ b/examples/image_bot/src/main.rs @@ -17,9 +17,14 @@ async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room, image: if text_content.body.contains("!image") { println!("sending image"); - room.send_attachment("cat", &mime::IMAGE_JPEG, image, AttachmentConfig::new()) - .await - .unwrap(); + room.send_attachment( + "cat.jpg", + &mime::IMAGE_JPEG, + image, + AttachmentConfig::new().caption(Some("my pretty cat".to_owned())), + ) + .await + .unwrap(); println!("message sent"); }