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.
22#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
23pub enum TimelineEventFilter {
24    /// Only return items whose event matches any of the conditions in the list.
25    Include(Vec<TimelineEventCondition>),
26    /// Return all items except the ones whose event matches any of the
27    /// conditions in the list
28    Exclude(Vec<TimelineEventCondition>),
29}
30
31impl TimelineEventFilter {
32    /// Filters any incoming `event` using the filter conditions.
33    ///
34    /// # Arguments
35    ///
36    /// * `event` - The event to run the filter on.
37    ///
38    /// # Returns
39    /// `true` if the filter allows the event or `false` otherwise.
40    pub fn filter(&self, event: &AnySyncTimelineEvent) -> bool {
41        match self {
42            Self::Include(conditions) => conditions.iter().any(|c| c.matches(event)),
43            Self::Exclude(conditions) => !conditions.iter().any(|c| c.matches(event)),
44        }
45    }
46}
47
48/// A condition that matches on an event's type or content.
49#[derive(Clone)]
50#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
51pub enum TimelineEventCondition {
52    /// The event has the specified event type.
53    EventType(TimelineEventType),
54    /// The event is an `m.room.member` event that represents a membership
55    /// change (join, leave, etc.).
56    MembershipChange(MembershipChangeFilter),
57    /// The event is an `m.room.member` event that represents a profile
58    /// change (displayname or avatar URL).
59    ProfileChange,
60    /// The event is a custom message-like event type.
61    AnyCustomMessageLikeEvent,
62    /// The event is a custom state event type.
63    AnyCustomStateEvent,
64}
65
66/// The membership states that should be included/excluded from the timeline
67/// item filters.
68#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
69#[derive(Clone)]
70pub enum MembershipChangeFilter {
71    /// Include/exclude all membership state events.
72    Any,
73    /// Include/exclude only `join` membership state events.
74    Join,
75    /// Include/exclude only `leave` membership state events.
76    Leave,
77    /// Include/exclude only `invite` membership state events.
78    Invite,
79    /// Include/exclude only `ban` membership state events.
80    Ban,
81    /// Include/exclude only `knock` membership state events.
82    Knock,
83}
84
85impl TimelineEventCondition {
86    /// Evaluate the condition against an event.
87    ///
88    /// # Arguments
89    ///
90    /// * `event` - The event to test the condition against.
91    ///
92    /// # Returns
93    /// `true` if the condition matches or `false` otherwise.
94    fn matches(&self, event: &AnySyncTimelineEvent) -> bool {
95        match self {
96            Self::EventType(event_type) => event.event_type() == *event_type,
97            Self::MembershipChange(filter) => match event {
98                AnySyncTimelineEvent::State(AnySyncStateEvent::RoomMember(
99                    SyncStateEvent::Original(ev),
100                )) => {
101                    if matches!(ev.membership_change(), MembershipChange::ProfileChanged { .. }) {
102                        return false;
103                    }
104                    match (filter, &ev.content.membership) {
105                        (MembershipChangeFilter::Any, _) => {
106                            !matches!(ev.membership_change(), MembershipChange::None)
107                        }
108                        (MembershipChangeFilter::Join, MembershipState::Join) => true,
109                        (MembershipChangeFilter::Invite, MembershipState::Invite) => true,
110                        (MembershipChangeFilter::Leave, MembershipState::Leave) => true,
111                        (MembershipChangeFilter::Knock, MembershipState::Knock) => true,
112                        (MembershipChangeFilter::Ban, MembershipState::Ban) => true,
113                        _ => false,
114                    }
115                }
116                _ => false,
117            },
118            Self::ProfileChange => match event {
119                AnySyncTimelineEvent::State(AnySyncStateEvent::RoomMember(
120                    SyncStateEvent::Original(ev),
121                )) => {
122                    matches!(ev.membership_change(), MembershipChange::ProfileChanged { .. })
123                }
124                _ => false,
125            },
126            Self::AnyCustomMessageLikeEvent => match event {
127                AnySyncTimelineEvent::MessageLike(_) => {
128                    matches!(event.event_type(), TimelineEventType::_Custom(_))
129                }
130                AnySyncTimelineEvent::State(_) => false,
131            },
132            Self::AnyCustomStateEvent => match event {
133                AnySyncTimelineEvent::State(_) => {
134                    matches!(event.event_type(), TimelineEventType::_Custom(_))
135                }
136                AnySyncTimelineEvent::MessageLike(_) => false,
137            },
138        }
139    }
140}