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}