Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(send queue): allow setting intentional mentions in media captions edits #4464

Merged
merged 1 commit into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions bindings/matrix-sdk-ffi/src/timeline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ use crate::{
event::EventOrTransactionId,
helpers::unwrap_or_clone_arc,
ruma::{
AssetType, AudioInfo, FileInfo, FormattedBody, ImageInfo, PollKind, ThumbnailInfo,
VideoInfo,
AssetType, AudioInfo, FileInfo, FormattedBody, ImageInfo, Mentions, PollKind,
ThumbnailInfo, VideoInfo,
},
task_handle::TaskHandle,
utils::Timestamp,
Expand Down Expand Up @@ -1295,22 +1295,32 @@ impl From<ReceiptType> for ruma::api::client::receipt::create_receipt::v3::Recei

#[derive(Clone, uniffi::Enum)]
pub enum EditedContent {
RoomMessage { content: Arc<RoomMessageEventContentWithoutRelation> },
MediaCaption { caption: Option<String>, formatted_caption: Option<FormattedBody> },
PollStart { poll_data: PollData },
RoomMessage {
content: Arc<RoomMessageEventContentWithoutRelation>,
},
MediaCaption {
caption: Option<String>,
formatted_caption: Option<FormattedBody>,
mentions: Option<Mentions>,
},
PollStart {
poll_data: PollData,
},
}

impl TryFrom<EditedContent> for SdkEditedContent {
type Error = ClientError;

fn try_from(value: EditedContent) -> Result<Self, Self::Error> {
match value {
EditedContent::RoomMessage { content } => {
Ok(SdkEditedContent::RoomMessage((*content).clone()))
}
EditedContent::MediaCaption { caption, formatted_caption } => {
EditedContent::MediaCaption { caption, formatted_caption, mentions } => {
Ok(SdkEditedContent::MediaCaption {
caption,
formatted_caption: formatted_caption.map(Into::into),
mentions: mentions.map(Into::into),
})
}
EditedContent::PollStart { poll_data } => {
Expand All @@ -1332,12 +1342,14 @@ impl TryFrom<EditedContent> for SdkEditedContent {
fn create_caption_edit(
caption: Option<String>,
formatted_caption: Option<FormattedBody>,
mentions: Option<Mentions>,
) -> EditedContent {
let formatted_caption =
formatted_body_from(caption.as_deref(), formatted_caption.map(Into::into));
EditedContent::MediaCaption {
caption,
formatted_caption: formatted_caption.as_ref().map(Into::into),
mentions,
}
}

Expand Down
4 changes: 2 additions & 2 deletions crates/matrix-sdk-ui/src/timeline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -486,9 +486,9 @@ impl Timeline {
}
}

EditedContent::MediaCaption { caption, formatted_caption } => {
EditedContent::MediaCaption { caption, formatted_caption, mentions } => {
if handle
.edit_media_caption(caption, formatted_caption)
.edit_media_caption(caption, formatted_caption, mentions)
.await
.map_err(RoomSendQueueError::StorageError)?
{
Expand Down
120 changes: 108 additions & 12 deletions crates/matrix-sdk/src/room/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ use ruma::{
RoomMessageEventContentWithoutRelation,
},
AnyMessageLikeEvent, AnyMessageLikeEventContent, AnySyncMessageLikeEvent,
AnySyncTimelineEvent, AnyTimelineEvent, MessageLikeEvent, OriginalMessageLikeEvent,
SyncMessageLikeEvent,
AnySyncTimelineEvent, AnyTimelineEvent, Mentions, MessageLikeEvent,
OriginalMessageLikeEvent, SyncMessageLikeEvent,
},
EventId, RoomId, UserId,
};
Expand All @@ -54,6 +54,10 @@ pub enum EditedContent {
///
/// Set to `None` to remove an existing formatted caption.
formatted_caption: Option<FormattedBody>,

/// New set of intentional mentions to be included in the edited
/// caption.
mentions: Option<Mentions>,
},

/// The content is a new poll start.
Expand Down Expand Up @@ -196,7 +200,7 @@ async fn make_edit_event<S: EventSource>(
Ok(replacement.into())
}

EditedContent::MediaCaption { caption, formatted_caption } => {
EditedContent::MediaCaption { caption, formatted_caption, mentions } => {
// Handle edits of m.room.message.
let AnySyncMessageLikeEvent::RoomMessage(SyncMessageLikeEvent::Original(original)) =
message_like_event
Expand All @@ -207,21 +211,21 @@ async fn make_edit_event<S: EventSource>(
});
};

let mentions = original.content.mentions.clone();
let original_mentions = original.content.mentions.clone();
let replied_to_original_room_msg =
extract_replied_to(source, room_id, original.content.relates_to.clone()).await;

let mut prev_content = original.content;

if !update_media_caption(&mut prev_content, caption, formatted_caption) {
if !update_media_caption(&mut prev_content, caption, formatted_caption, mentions) {
return Err(EditError::IncompatibleEditType {
target: prev_content.msgtype.msgtype().to_owned(),
new_content: "caption for a media room message",
});
}

let replacement = prev_content.make_replacement(
ReplacementMetadata::new(event_id.to_owned(), mentions),
ReplacementMetadata::new(event_id.to_owned(), original_mentions),
replied_to_original_room_msg.as_ref(),
);

Expand Down Expand Up @@ -282,7 +286,10 @@ pub(crate) fn update_media_caption(
content: &mut RoomMessageEventContent,
caption: Option<String>,
formatted_caption: Option<FormattedBody>,
mentions: Option<Mentions>,
) -> bool {
content.mentions = mentions;

match &mut content.msgtype {
MessageType::Audio(event) => {
set_caption!(event, caption);
Expand Down Expand Up @@ -358,9 +365,9 @@ mod tests {
event_id,
events::{
room::message::{MessageType, Relation, RoomMessageEventContentWithoutRelation},
AnyMessageLikeEventContent, AnySyncTimelineEvent,
AnyMessageLikeEventContent, AnySyncTimelineEvent, Mentions,
},
owned_mxc_uri, room_id,
owned_mxc_uri, owned_user_id, room_id,
serde::Raw,
user_id, EventId, OwnedEventId,
};
Expand Down Expand Up @@ -506,7 +513,11 @@ mod tests {
room_id,
own_user_id,
event_id,
EditedContent::MediaCaption { caption: Some("yo".to_owned()), formatted_caption: None },
EditedContent::MediaCaption {
caption: Some("yo".to_owned()),
formatted_caption: None,
mentions: None,
},
)
.await
.unwrap_err();
Expand Down Expand Up @@ -543,6 +554,7 @@ mod tests {
EditedContent::MediaCaption {
caption: Some("Best joke ever".to_owned()),
formatted_caption: None,
mentions: None,
},
)
.await
Expand Down Expand Up @@ -572,12 +584,12 @@ mod tests {
let mut cache = TestEventCache::default();
let f = EventFactory::new();

let event: SyncTimelineEvent = f
let event = f
.image(filename.to_owned(), owned_mxc_uri!("mxc://sdk.rs/rickroll"))
.caption(Some("caption".to_owned()), None)
.event_id(event_id)
.sender(own_user_id)
.into();
.into_sync();

{
// Sanity checks.
Expand All @@ -602,7 +614,7 @@ mod tests {
own_user_id,
event_id,
// Remove the caption by setting it to None.
EditedContent::MediaCaption { caption: None, formatted_caption: None },
EditedContent::MediaCaption { caption: None, formatted_caption: None, mentions: None },
)
.await
.unwrap();
Expand All @@ -621,6 +633,90 @@ mod tests {
assert!(new_image.formatted_caption().is_none());
}

#[async_test]
async fn test_add_media_caption_mention() {
let event_id = event_id!("$1");
let own_user_id = user_id!("@me:saucisse.bzh");

let filename = "rickroll.gif";

let mut cache = TestEventCache::default();
let f = EventFactory::new();

// Start with a media event that has no mentions.
let event = f
.image(filename.to_owned(), owned_mxc_uri!("mxc://sdk.rs/rickroll"))
.event_id(event_id)
.sender(own_user_id)
.into_sync();

{
// Sanity checks.
let event = event.raw().deserialize().unwrap();
assert_let!(AnySyncTimelineEvent::MessageLike(event) = event);
assert_let!(
AnyMessageLikeEventContent::RoomMessage(msg) = event.original_content().unwrap()
);
assert_matches!(msg.mentions, None);
}

cache.events.insert(event_id.to_owned(), event);

let room_id = room_id!("!galette:saucisse.bzh");

// Add an intentional mention in the caption.
let mentioned_user_id = owned_user_id!("@crepe:saucisse.bzh");
let edit_event = {
let mentions = Mentions::with_user_ids([mentioned_user_id.clone()]);
make_edit_event(
cache,
room_id,
own_user_id,
event_id,
EditedContent::MediaCaption {
caption: None,
formatted_caption: None,
mentions: Some(mentions),
},
)
.await
.unwrap()
};

assert_let!(AnyMessageLikeEventContent::RoomMessage(msg) = edit_event);
assert_let!(MessageType::Image(image) = msg.msgtype);

assert!(image.caption().is_none());
assert!(image.formatted_caption().is_none());

// The raw event doesn't contain the mention :(
// TODO: this is a bug in Ruma! When Ruma gets upgraded in the SDK, this test
// may start failing. In this case, remove the following code, and replace it
// with the commented code below.

assert_matches!(msg.mentions, None);

/*
// The raw event contains the mention.
assert_let!(Some(mentions) = msg.mentions);
assert!(!mentions.room);
assert_eq!(
mentions.user_ids.into_iter().collect::<Vec<_>>(),
vec![mentioned_user_id.clone()]
);
*/

assert_let!(Some(Relation::Replacement(repl)) = msg.relates_to);
assert_let!(MessageType::Image(new_image) = repl.new_content.msgtype);
assert!(new_image.caption().is_none());
assert!(new_image.formatted_caption().is_none());

// The replacement contains the mention.
assert_let!(Some(mentions) = repl.new_content.mentions);
assert!(!mentions.room);
assert_eq!(mentions.user_ids.into_iter().collect::<Vec<_>>(), vec![mentioned_user_id]);
}

#[async_test]
async fn test_make_edit_event_success_with_response() {
let event_id = event_id!("$1");
Expand Down
5 changes: 3 additions & 2 deletions crates/matrix-sdk/src/send_queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ use ruma::{
message::{FormattedBody, RoomMessageEventContent},
MediaSource,
},
AnyMessageLikeEventContent, EventContent as _,
AnyMessageLikeEventContent, EventContent as _, Mentions,
},
serde::Raw,
OwnedEventId, OwnedRoomId, OwnedTransactionId, TransactionId,
Expand Down Expand Up @@ -1996,12 +1996,13 @@ impl SendHandle {
&self,
caption: Option<String>,
formatted_caption: Option<FormattedBody>,
mentions: Option<Mentions>,
) -> Result<bool, RoomSendQueueStorageError> {
if let Some(new_content) = self
.room
.inner
.queue
.edit_media_caption(&self.transaction_id, caption, formatted_caption)
.edit_media_caption(&self.transaction_id, caption, formatted_caption, mentions)
.await?
{
trace!("successful edit of media caption");
Expand Down
7 changes: 4 additions & 3 deletions crates/matrix-sdk/src/send_queue/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use mime::Mime;
use ruma::{
events::{
room::message::{FormattedBody, MessageType, RoomMessageEventContent},
AnyMessageLikeEventContent,
AnyMessageLikeEventContent, Mentions,
},
OwnedTransactionId, TransactionId,
};
Expand Down Expand Up @@ -488,6 +488,7 @@ impl QueueStorage {
txn: &TransactionId,
caption: Option<String>,
formatted_caption: Option<FormattedBody>,
mentions: Option<Mentions>,
) -> Result<Option<AnyMessageLikeEventContent>, RoomSendQueueStorageError> {
// This error will be popular here.
use RoomSendQueueStorageError::InvalidMediaCaptionEdit;
Expand Down Expand Up @@ -522,7 +523,7 @@ impl QueueStorage {
return Err(InvalidMediaCaptionEdit);
};

if !update_media_caption(&mut local_echo, caption, formatted_caption) {
if !update_media_caption(&mut local_echo, caption, formatted_caption, mentions) {
return Err(InvalidMediaCaptionEdit);
}

Expand Down Expand Up @@ -561,7 +562,7 @@ impl QueueStorage {
return Err(InvalidMediaCaptionEdit);
};

if !update_media_caption(&mut content, caption, formatted_caption) {
if !update_media_caption(&mut content, caption, formatted_caption, mentions) {
return Err(InvalidMediaCaptionEdit);
}

Expand Down
Loading
Loading