mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2025-12-24 00:01:03 -05:00
feat(ui): add custom events to timeline when explicitly filtered
This allows custom message-like events (created by the `EventContent` macro from ruma) to be added to the timeline if they are explicitly allowed when building the timeline with a custom `event_filter`. The custom event content is not available directly to the consumer, but it can still fetch it from the matrix-sdk client with its `event_id`, or display a "this type of event is not supported". Signed-off-by: Itess <me@aloiseau.com> Fixes #5598.
This commit is contained in:
@@ -380,6 +380,7 @@ pub enum MessageLikeEventType {
|
||||
UnstablePollEnd,
|
||||
UnstablePollResponse,
|
||||
UnstablePollStart,
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl From<MessageLikeEventType> for ruma::events::MessageLikeEventType {
|
||||
@@ -408,6 +409,7 @@ impl From<MessageLikeEventType> for ruma::events::MessageLikeEventType {
|
||||
MessageLikeEventType::UnstablePollEnd => Self::UnstablePollEnd,
|
||||
MessageLikeEventType::UnstablePollResponse => Self::UnstablePollResponse,
|
||||
MessageLikeEventType::UnstablePollStart => Self::UnstablePollStart,
|
||||
MessageLikeEventType::Other(msgtype) => Self::from(msgtype),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ use super::{
|
||||
};
|
||||
use crate::{
|
||||
error::ClientError,
|
||||
event::MessageLikeEventType,
|
||||
ruma::{ImageInfo, MediaSource, MediaSourceExt, Mentions, MessageType, PollKind},
|
||||
timeline::content::ReactionSenderData,
|
||||
utils::Timestamp,
|
||||
@@ -50,6 +51,9 @@ pub enum MsgLikeKind {
|
||||
|
||||
/// An `m.room.encrypted` event that could not be decrypted.
|
||||
UnableToDecrypt { msg: EncryptedMessage },
|
||||
|
||||
/// A custom message like event.
|
||||
Other { event_type: MessageLikeEventType },
|
||||
}
|
||||
|
||||
/// A special kind of [`super::TimelineItemContent`] that groups together
|
||||
@@ -182,6 +186,15 @@ impl TryFrom<matrix_sdk_ui::timeline::MsgLikeContent> for MsgLikeContent {
|
||||
thread_root,
|
||||
thread_summary,
|
||||
},
|
||||
Kind::Other(other) => Self {
|
||||
kind: MsgLikeKind::Other {
|
||||
event_type: MessageLikeEventType::Other(other.event_type().to_string()),
|
||||
},
|
||||
reactions,
|
||||
in_reply_to,
|
||||
thread_root,
|
||||
thread_summary,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,8 @@ use super::{
|
||||
traits::RoomDataProvider,
|
||||
};
|
||||
use crate::{
|
||||
timeline::controller::aggregations::PendingEdit, unable_to_decrypt_hook::UtdHookManager,
|
||||
timeline::{controller::aggregations::PendingEdit, event_item::OtherMessageLike},
|
||||
unable_to_decrypt_hook::UtdHookManager,
|
||||
};
|
||||
|
||||
/// When adding an event, useful information related to the source of the event.
|
||||
@@ -381,12 +382,18 @@ impl TimelineAction {
|
||||
),
|
||||
},
|
||||
|
||||
_ => {
|
||||
debug!(
|
||||
"Ignoring message-like event of type `{}`, not supported (yet)",
|
||||
content.event_type()
|
||||
);
|
||||
return None;
|
||||
event => {
|
||||
let other = OtherMessageLike { event_type: event.event_type() };
|
||||
|
||||
Self::AddItem {
|
||||
content: TimelineItemContent::MsgLike(MsgLikeContent {
|
||||
kind: MsgLikeKind::Other(other),
|
||||
reactions: Default::default(),
|
||||
thread_root,
|
||||
in_reply_to,
|
||||
thread_summary,
|
||||
}),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ use tracing::warn;
|
||||
|
||||
mod message;
|
||||
mod msg_like;
|
||||
pub(super) mod other;
|
||||
pub(crate) mod pinned_events;
|
||||
mod polls;
|
||||
mod reply;
|
||||
@@ -74,6 +75,7 @@ pub(in crate::timeline) use self::message::{
|
||||
pub use self::{
|
||||
message::Message,
|
||||
msg_like::{MsgLikeContent, MsgLikeKind, ThreadSummary},
|
||||
other::OtherMessageLike,
|
||||
polls::{PollResult, PollState},
|
||||
reply::{EmbeddedEvent, InReplyToDetails},
|
||||
};
|
||||
|
||||
@@ -16,7 +16,9 @@ use as_variant::as_variant;
|
||||
use ruma::OwnedEventId;
|
||||
|
||||
use super::{EmbeddedEvent, EncryptedMessage, InReplyToDetails, Message, PollState, Sticker};
|
||||
use crate::timeline::{ReactionsByKeyBySender, TimelineDetails};
|
||||
use crate::timeline::{
|
||||
ReactionsByKeyBySender, TimelineDetails, event_item::content::other::OtherMessageLike,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum MsgLikeKind {
|
||||
@@ -34,6 +36,9 @@ pub enum MsgLikeKind {
|
||||
|
||||
/// An `m.room.encrypted` event that could not be decrypted.
|
||||
UnableToDecrypt(EncryptedMessage),
|
||||
|
||||
/// A custom message like event.
|
||||
Other(OtherMessageLike),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -74,6 +79,7 @@ impl MsgLikeContent {
|
||||
MsgLikeKind::Poll(_) => "a poll",
|
||||
MsgLikeKind::Redacted => "a redacted message",
|
||||
MsgLikeKind::UnableToDecrypt(_) => "an encrypted message we couldn't decrypt",
|
||||
MsgLikeKind::Other(_) => "a custom message-like event",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2025 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Timeline item content for other message-like events created by the
|
||||
//! EventContent macro from ruma.
|
||||
|
||||
use ruma::events::MessageLikeEventType;
|
||||
|
||||
/// A custom event created by the EventContent macro from ruma.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct OtherMessageLike {
|
||||
pub(in crate::timeline) event_type: MessageLikeEventType,
|
||||
}
|
||||
|
||||
impl OtherMessageLike {
|
||||
pub fn from_event_type(event_type: MessageLikeEventType) -> Self {
|
||||
Self { event_type }
|
||||
}
|
||||
|
||||
/// Get the event_type of this message.
|
||||
pub fn event_type(&self) -> &MessageLikeEventType {
|
||||
&self.event_type
|
||||
}
|
||||
}
|
||||
@@ -46,9 +46,9 @@ mod remote;
|
||||
pub use self::{
|
||||
content::{
|
||||
AnyOtherFullStateEventContent, EmbeddedEvent, EncryptedMessage, InReplyToDetails,
|
||||
MemberProfileChange, MembershipChange, Message, MsgLikeContent, MsgLikeKind, OtherState,
|
||||
PollResult, PollState, RoomMembershipChange, RoomPinnedEventsChange, Sticker,
|
||||
ThreadSummary, TimelineItemContent,
|
||||
MemberProfileChange, MembershipChange, Message, MsgLikeContent, MsgLikeKind,
|
||||
OtherMessageLike, OtherState, PollResult, PollState, RoomMembershipChange,
|
||||
RoomPinnedEventsChange, Sticker, ThreadSummary, TimelineItemContent,
|
||||
},
|
||||
local::{EventSendState, MediaUploadProgress},
|
||||
};
|
||||
@@ -608,7 +608,8 @@ impl EventTimelineItem {
|
||||
MsgLikeKind::Sticker(_)
|
||||
| MsgLikeKind::Poll(_)
|
||||
| MsgLikeKind::Redacted
|
||||
| MsgLikeKind::UnableToDecrypt(_) => None,
|
||||
| MsgLikeKind::UnableToDecrypt(_)
|
||||
| MsgLikeKind::Other(_) => None,
|
||||
},
|
||||
TimelineItemContent::MembershipChange(_)
|
||||
| TimelineItemContent::ProfileChange(_)
|
||||
|
||||
@@ -95,10 +95,10 @@ pub use self::{
|
||||
event_item::{
|
||||
AnyOtherFullStateEventContent, EmbeddedEvent, EncryptedMessage, EventItemOrigin,
|
||||
EventSendState, EventTimelineItem, InReplyToDetails, MediaUploadProgress,
|
||||
MemberProfileChange, MembershipChange, Message, MsgLikeContent, MsgLikeKind, OtherState,
|
||||
PollResult, PollState, Profile, ReactionInfo, ReactionStatus, ReactionsByKeyBySender,
|
||||
RoomMembershipChange, RoomPinnedEventsChange, Sticker, ThreadSummary, TimelineDetails,
|
||||
TimelineEventItemId, TimelineItemContent,
|
||||
MemberProfileChange, MembershipChange, Message, MsgLikeContent, MsgLikeKind,
|
||||
OtherMessageLike, OtherState, PollResult, PollState, Profile, ReactionInfo, ReactionStatus,
|
||||
ReactionsByKeyBySender, RoomMembershipChange, RoomPinnedEventsChange, Sticker,
|
||||
ThreadSummary, TimelineDetails, TimelineEventItemId, TimelineItemContent,
|
||||
},
|
||||
event_type_filter::TimelineEventTypeFilter,
|
||||
item::{TimelineItem, TimelineItemKind, TimelineUniqueId},
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use assert_matches2::assert_let;
|
||||
use eyeball_im::VectorDiff;
|
||||
use matrix_sdk::deserialized_responses::TimelineEvent;
|
||||
@@ -137,6 +138,31 @@ async fn test_custom_filter() {
|
||||
assert_eq!(timeline.controller.items().await.len(), 3);
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_custom_filter_for_custom_msglike_event() {
|
||||
// Filter out all state events.
|
||||
let timeline = TestTimelineBuilder::new()
|
||||
.settings(TimelineSettings {
|
||||
event_filter: Arc::new(|ev, _| matches!(ev, AnySyncTimelineEvent::MessageLike(_))),
|
||||
..Default::default()
|
||||
})
|
||||
.build();
|
||||
let mut stream = timeline.subscribe().await;
|
||||
|
||||
let f = &timeline.factory;
|
||||
timeline.handle_live_event(f.custom_message_like_event().sender(&ALICE)).await;
|
||||
let item = assert_next_matches!(stream, VectorDiff::PushBack { value } => value);
|
||||
let date_divider = assert_next_matches!(stream, VectorDiff::PushFront { value } => value);
|
||||
|
||||
assert_matches!(
|
||||
item.as_event().unwrap().content().as_msglike().unwrap().kind.clone(),
|
||||
MsgLikeKind::Other(_)
|
||||
);
|
||||
assert!(date_divider.is_date_divider());
|
||||
|
||||
assert_eq!(timeline.controller.items().await.len(), 2);
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_hide_failed_to_parse() {
|
||||
let timeline = TestTimelineBuilder::new()
|
||||
|
||||
@@ -29,16 +29,19 @@ use matrix_sdk_test::{
|
||||
use matrix_sdk_ui::{
|
||||
Timeline,
|
||||
timeline::{
|
||||
AnyOtherFullStateEventContent, Error, EventSendState, RedactError, RoomExt,
|
||||
TimelineBuilder, TimelineEventItemId, TimelineFocus, TimelineItemContent,
|
||||
VirtualTimelineItem,
|
||||
AnyOtherFullStateEventContent, Error, EventSendState, MsgLikeKind, OtherMessageLike,
|
||||
RedactError, RoomExt, TimelineBuilder, TimelineEventItemId, TimelineFocus,
|
||||
TimelineItemContent, VirtualTimelineItem, default_event_filter,
|
||||
},
|
||||
};
|
||||
use ruma::{
|
||||
EventId, MilliSecondsSinceUnixEpoch, event_id,
|
||||
events::room::{
|
||||
encryption::RoomEncryptionEventContent,
|
||||
message::{RedactedRoomMessageEventContent, RoomMessageEventContent},
|
||||
events::{
|
||||
MessageLikeEventType, TimelineEventType,
|
||||
room::{
|
||||
encryption::RoomEncryptionEventContent,
|
||||
message::{RedactedRoomMessageEventContent, RoomMessageEventContent},
|
||||
},
|
||||
},
|
||||
owned_event_id, room_id, user_id,
|
||||
};
|
||||
@@ -884,6 +887,50 @@ async fn test_timeline_receives_a_limited_number_of_events_when_subscribing() {
|
||||
assert_pending!(timeline_stream);
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_custom_msglike_event_in_timeline() {
|
||||
let server = MatrixMockServer::new().await;
|
||||
let client = server.client_builder().build().await;
|
||||
|
||||
let room_id = room_id!("!a98sd12bjh:example.org");
|
||||
let room = server.sync_joined_room(&client, room_id).await;
|
||||
|
||||
server.mock_room_state_encryption().plain().mount().await;
|
||||
|
||||
let timeline = room
|
||||
.timeline_builder()
|
||||
.event_filter(|event, room_version| {
|
||||
event.event_type() == TimelineEventType::from("rs.matrix-sdk.custom.test")
|
||||
|| default_event_filter(event, room_version)
|
||||
})
|
||||
.build()
|
||||
.await
|
||||
.unwrap();
|
||||
let (_, mut timeline_stream) = timeline.subscribe().await;
|
||||
|
||||
let event_id = event_id!("$eeG0HA0FAZ37wP8kXlNk123I");
|
||||
let f = EventFactory::new();
|
||||
server
|
||||
.sync_room(
|
||||
&client,
|
||||
JoinedRoomBuilder::new(room_id).add_timeline_event(
|
||||
f.custom_message_like_event().event_id(event_id).sender(user_id!("@a:b.c")),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_let!(Some(timeline_updates) = timeline_stream.next().await);
|
||||
assert_let!(VectorDiff::PushBack { value: first } = &timeline_updates[0]);
|
||||
let event_type = MessageLikeEventType::from("rs.matrix-sdk.custom.test");
|
||||
let other_msglike = OtherMessageLike::from_event_type(event_type);
|
||||
assert_matches!(
|
||||
first.as_event().unwrap().content().as_msglike().unwrap().kind.clone(),
|
||||
MsgLikeKind::Other(observed_other) => {
|
||||
assert_eq!(observed_other, other_msglike);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
struct PinningTestSetup<'a> {
|
||||
event_id: &'a EventId,
|
||||
room_id: &'a ruma::RoomId,
|
||||
|
||||
@@ -123,6 +123,10 @@ fn format_timeline_item(item: &Arc<TimelineItem>, is_thread: bool) -> Option<Lis
|
||||
kind: MsgLikeKind::Sticker(_),
|
||||
..
|
||||
})
|
||||
| TimelineItemContent::MsgLike(MsgLikeContent {
|
||||
kind: MsgLikeKind::Other(_),
|
||||
..
|
||||
})
|
||||
| TimelineItemContent::ProfileChange(_)
|
||||
| TimelineItemContent::OtherState(_)
|
||||
| TimelineItemContent::FailedToParseMessageLike { .. }
|
||||
|
||||
@@ -37,6 +37,7 @@ use ruma::{
|
||||
call::{SessionDescription, invite::CallInviteEventContent},
|
||||
direct::{DirectEventContent, OwnedDirectUserIdentifier},
|
||||
ignored_user_list::IgnoredUserListEventContent,
|
||||
macros::EventContent,
|
||||
member_hints::MemberHintsEventContent,
|
||||
poll::{
|
||||
unstable_end::UnstablePollEndEventContent,
|
||||
@@ -1076,6 +1077,11 @@ impl EventFactory {
|
||||
event
|
||||
}
|
||||
|
||||
/// Create a new `rs.matrix-sdk.custom.test` custom event
|
||||
pub fn custom_message_like_event(&self) -> EventBuilder<CustomMessageLikeEventContent> {
|
||||
self.event(CustomMessageLikeEventContent)
|
||||
}
|
||||
|
||||
/// Set the next server timestamp.
|
||||
///
|
||||
/// Timestamps will continue to increase by 1 (millisecond) from that value.
|
||||
@@ -1297,3 +1303,7 @@ impl From<MembershipState> for PreviousMembership {
|
||||
Self::new(state)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, Serialize, EventContent)]
|
||||
#[ruma_event(type = "rs.matrix-sdk.custom.test", kind = MessageLike)]
|
||||
pub struct CustomMessageLikeEventContent;
|
||||
|
||||
Reference in New Issue
Block a user