Skip to main content

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, MembershipState},
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(MembershipChangeFilter),
55    /// The event is an `m.room.member` event that represents a profile
56    /// change (displayname or avatar URL).
57    ProfileChange,
58}
59
60/// The membership states that should be included/excluded from the timeline
61/// item filters.
62#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
63#[derive(Clone)]
64pub enum MembershipChangeFilter {
65    /// Include/exclude all membership state events.
66    Any,
67    /// Include/exclude only `join` membership state events.
68    Join,
69    /// Include/exclude only `leave` membership state events.
70    Leave,
71    /// Include/exclude only `invite` membership state events.
72    Invite,
73    /// Include/exclude only `ban` membership state events.
74    Ban,
75    /// Include/exclude only `knock` membership state events.
76    Knock,
77}
78
79impl TimelineEventCondition {
80    /// Evaluate the condition against an event.
81    ///
82    /// # Arguments
83    ///
84    /// * `event` - The event to test the condition against.
85    ///
86    /// # Returns
87    /// `true` if the condition matches or `false` otherwise.
88    fn matches(&self, event: &AnySyncTimelineEvent) -> bool {
89        match self {
90            Self::EventType(event_type) => event.event_type() == *event_type,
91            Self::MembershipChange(filter) => match event {
92                AnySyncTimelineEvent::State(AnySyncStateEvent::RoomMember(
93                    SyncStateEvent::Original(ev),
94                )) => {
95                    if matches!(ev.membership_change(), MembershipChange::ProfileChanged { .. }) {
96                        return false;
97                    }
98                    match (filter, &ev.content.membership) {
99                        (MembershipChangeFilter::Any, _) => {
100                            !matches!(ev.membership_change(), MembershipChange::None)
101                        }
102                        (MembershipChangeFilter::Join, MembershipState::Join) => true,
103                        (MembershipChangeFilter::Invite, MembershipState::Invite) => true,
104                        (MembershipChangeFilter::Leave, MembershipState::Leave) => true,
105                        (MembershipChangeFilter::Knock, MembershipState::Knock) => true,
106                        (MembershipChangeFilter::Ban, MembershipState::Ban) => true,
107                        _ => false,
108                    }
109                }
110                _ => false,
111            },
112            Self::ProfileChange => match event {
113                AnySyncTimelineEvent::State(AnySyncStateEvent::RoomMember(
114                    SyncStateEvent::Original(ev),
115                )) => {
116                    matches!(ev.membership_change(), MembershipChange::ProfileChanged { .. })
117                }
118                _ => false,
119            },
120        }
121    }
122}