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