matrix_sdk_ui/timeline/event_filter.rs
1// Copyright 2026 The Matrix.org Foundation C.I.C.
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
15use ruma::events::{
16 AnySyncStateEvent, AnySyncTimelineEvent, SyncStateEvent, TimelineEventType,
17 room::member::MembershipChange,
18};
19
20/// A timeline filter that in- or excludes events based on their type or
21/// content.
22pub enum TimelineEventFilter {
23 /// Only return items whose event matches any of the conditions in the list.
24 Include(Vec<TimelineEventCondition>),
25 /// Return all items except the ones whose event matches any of the
26 /// conditions in the list
27 Exclude(Vec<TimelineEventCondition>),
28}
29
30impl TimelineEventFilter {
31 /// Filters any incoming `event` using the filter conditions.
32 ///
33 /// # Arguments
34 ///
35 /// * `event` - The event to run the filter on.
36 ///
37 /// # Returns
38 /// `true` if the filter allows the event or `false` otherwise.
39 pub fn filter(&self, event: &AnySyncTimelineEvent) -> bool {
40 match self {
41 Self::Include(conditions) => conditions.iter().any(|c| c.matches(event)),
42 Self::Exclude(conditions) => !conditions.iter().any(|c| c.matches(event)),
43 }
44 }
45}
46
47/// A condition that matches on an event's type or content.
48#[derive(Clone)]
49pub enum TimelineEventCondition {
50 /// The event has the specified event type.
51 EventType(TimelineEventType),
52 /// The event is an `m.room.member` event that represents a membership
53 /// change (join, leave, etc.).
54 MembershipChange,
55 /// The event is an `m.room.member` event that represents a profile
56 /// change (displayname or avatar URL).
57 ProfileChange,
58}
59
60impl TimelineEventCondition {
61 /// Evaluate the condition against an event.
62 ///
63 /// # Arguments
64 ///
65 /// * `event` - The event to test the condition against.
66 ///
67 /// # Returns
68 /// `true` if the condition matches or `false` otherwise.
69 fn matches(&self, event: &AnySyncTimelineEvent) -> bool {
70 match self {
71 Self::EventType(event_type) => event.event_type() == *event_type,
72 Self::MembershipChange => match event {
73 AnySyncTimelineEvent::State(AnySyncStateEvent::RoomMember(
74 SyncStateEvent::Original(ev),
75 )) => {
76 let change = ev.content.membership_change(
77 ev.prev_content().as_ref().map(|c| c.details()),
78 &ev.sender,
79 &ev.state_key,
80 );
81 !matches!(change, MembershipChange::ProfileChanged { .. })
82 }
83 _ => false,
84 },
85 Self::ProfileChange => match event {
86 AnySyncTimelineEvent::State(AnySyncStateEvent::RoomMember(
87 SyncStateEvent::Original(ev),
88 )) => {
89 let change = ev.content.membership_change(
90 ev.prev_content().as_ref().map(|c| c.details()),
91 &ev.sender,
92 &ev.state_key,
93 );
94 matches!(change, MembershipChange::ProfileChanged { .. })
95 }
96 _ => false,
97 },
98 }
99 }
100}