From cc585974e0d09987bb0bdba5daa5b1a729ca83e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Tue, 14 Mar 2023 12:13:29 +0100 Subject: [PATCH] sdk: Get push actions of events received via back pagination MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kévin Commaille --- crates/matrix-sdk/src/room/common.rs | 56 ++++++++++- .../tests/integration/room/timeline/mod.rs | 96 +++++++++++++++++++ 2 files changed, 151 insertions(+), 1 deletion(-) diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index f37f0c6fd..aab0694b3 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -28,10 +28,12 @@ use ruma::{ assign, events::{ direct::DirectEventContent, + push_rules::PushRulesEventContent, receipt::{Receipt, ReceiptThread, ReceiptType}, room::{ encryption::RoomEncryptionEventContent, history_visibility::HistoryVisibility, - server_acl::RoomServerAclEventContent, MediaSource, + power_levels::RoomPowerLevelsEventContent, server_acl::RoomServerAclEventContent, + MediaSource, }, tag::{TagInfo, TagName}, AnyRoomAccountDataEvent, AnyStateEvent, AnySyncStateEvent, EmptyStateKey, RedactContent, @@ -39,6 +41,7 @@ use ruma::{ RoomAccountDataEventType, StateEventType, StaticEventContent, StaticStateEventContent, SyncStateEvent, }, + push::{PushConditionRoomCtx, Ruleset}, serde::Raw, uint, EventId, MatrixToUri, MatrixUri, OwnedEventId, OwnedServerName, OwnedUserId, RoomId, UInt, UserId, @@ -236,6 +239,20 @@ impl Common { response.chunk.extend(http_response.chunk.into_iter().map(TimelineEvent::new)); } + if let Some(push_context) = self.push_context().await? { + let push_rules = self + .client() + .account() + .account_data::() + .await? + .and_then(|r| r.deserialize().ok().map(|r| r.global)) + .unwrap_or_else(|| Ruleset::server_default(self.own_user_id())); + + for event in &mut response.chunk { + event.push_actions = push_rules.get_actions(&event.event, &push_context).to_owned(); + } + } + Ok(response) } @@ -1008,6 +1025,43 @@ impl Common { ) -> Result> { self.inner.event_receipts(receipt_type, thread, event_id).await.map_err(Into::into) } + + /// Get the push context for this room. + /// + /// Returns `None` if some data couldn't be found. This should only happen + /// in brand new rooms, while we process its state. + async fn push_context(&self) -> Result> { + let room_id = self.room_id(); + let user_id = self.own_user_id(); + let room_info = self.clone_info(); + let member_count = room_info.active_members_count(); + + let user_display_name = if let Some(member) = self.get_member_no_sync(user_id).await? { + member.name().to_owned() + } else { + return Ok(None); + }; + + let room_power_levels = if let Some(event) = self + .get_state_event_static::() + .await? + .and_then(|e| e.deserialize().ok()) + { + event.power_levels() + } else { + return Ok(None); + }; + + Ok(Some(PushConditionRoomCtx { + user_id: user_id.to_owned(), + room_id: room_id.to_owned(), + member_count: UInt::new(member_count).unwrap_or(UInt::MAX), + user_display_name, + users_power_levels: room_power_levels.users, + default_power_level: room_power_levels.users_default, + notification_power_levels: room_power_levels.notifications, + })) + } } /// Options for [`messages`][Common::messages]. diff --git a/crates/matrix-sdk/tests/integration/room/timeline/mod.rs b/crates/matrix-sdk/tests/integration/room/timeline/mod.rs index cd520710a..f43107f47 100644 --- a/crates/matrix-sdk/tests/integration/room/timeline/mod.rs +++ b/crates/matrix-sdk/tests/integration/room/timeline/mod.rs @@ -812,3 +812,99 @@ async fn sync_highlighted() { // `m.room.tombstone` should be highlighted by default. assert!(remote_event.is_highlighted()); } + +#[async_test] +async fn back_pagination_highlighted() { + let room_id = room_id!("!a98sd12bjh:example.org"); + let (client, server) = logged_in_client().await; + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let mut ev_builder = EventBuilder::new(); + ev_builder + // We need the member event and power levels locally so the push rules processor works. + .add_joined_room( + JoinedRoomBuilder::new(room_id) + .add_state_event(StateTestEvent::Member) + .add_state_event(StateTestEvent::PowerLevels), + ); + + mock_sync(&server, ev_builder.build_json_sync_response(), None).await; + let _response = client.sync_once(sync_settings.clone()).await.unwrap(); + server.reset().await; + + let room = client.get_room(room_id).unwrap(); + let timeline = Arc::new(room.timeline().await); + let (_, mut timeline_stream) = timeline.subscribe().await; + + let response_json = json!({ + "chunk": [ + { + "content": { + "body": "hello", + "msgtype": "m.text", + }, + "event_id": "$msda7m0df9E9op3", + "origin_server_ts": 152037280, + "sender": "@example:localhost", + "type": "m.room.message", + "room_id": room_id, + }, + { + "content": { + "body": "This room has been replaced", + "replacement_room": "!newroom:localhost", + }, + "event_id": "$foun39djjod0f", + "origin_server_ts": 152039280, + "sender": "@bob:localhost", + "state_key": "", + "type": "m.room.tombstone", + "room_id": room_id, + }, + ], + "end": "t47409-4357353_219380_26003_2269", + "start": "t392-516_47314_0_7_1_1_1_11444_1" + }); + Mock::given(method("GET")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/messages$")) + .and(header("authorization", "Bearer 1234")) + .respond_with(ResponseTemplate::new(200).set_body_json(response_json)) + .expect(1) + .named("messages_batch_1") + .mount(&server) + .await; + + timeline.paginate_backwards(PaginationOptions::single_request(10)).await.unwrap(); + server.reset().await; + + let loading = assert_matches!( + timeline_stream.next().await, + Some(VectorDiff::PushFront { value }) => value + ); + assert_matches!(loading.as_virtual().unwrap(), VirtualTimelineItem::LoadingIndicator); + + let day_divider = assert_matches!( + timeline_stream.next().await, + Some(VectorDiff::Insert { index: 1, value }) => value + ); + assert_matches!(day_divider.as_virtual().unwrap(), VirtualTimelineItem::DayDivider(_)); + + let first = assert_matches!( + timeline_stream.next().await, + Some(VectorDiff::Insert { index: 2, value }) => value + ); + let remote_event = first.as_event().unwrap().as_remote().unwrap(); + // Own events don't trigger push rules. + assert!(!remote_event.is_highlighted()); + + let second = assert_matches!( + timeline_stream.next().await, + Some(VectorDiff::Insert { index: 2, value }) => value + ); + let remote_event = second.as_event().unwrap().as_remote().unwrap(); + // `m.room.tombstone` should be highlighted by default. + assert!(remote_event.is_highlighted()); + + // Removal of the loading indicator + assert_matches!(timeline_stream.next().await, Some(VectorDiff::PopFront)); +}