atagen

macltok

public
Star0
Watch0
fa51490b6ae98fdf5eeddb0799f4e3a160bf53cb
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/normalise/room.rsRust246 lines8.9 KB
1
use futures_util::{StreamExt, stream};
2
use matrix_sdk::{
3
    Client, Room, RoomState,
4
    deserialized_responses::SyncOrStrippedState,
5
    latest_events::LatestEventValue,
6
    ruma::events::{
7
        AnyMessageLikeEventContent, AnySyncMessageLikeEvent, AnySyncTimelineEvent, SyncStateEvent,
8
        room::message::{Relation, RoomMessageEventContent},
9
        space::child::SpaceChildEventContent,
10
    },
11
};
12
use matrix_sdk_ui::spaces::{SpaceRoom, SpaceService};
13
use ruma::{
14
    OwnedRoomId,
15
    serde::{JsonObject, Raw},
16
};
17
18
use super::message_kind;
19
use crate::{
20
    LatestEvent, MediaId, MessageKind, RoomHero, RoomMembership, RoomNotificationMode, RoomSummary,
21
    SendState, SpaceChild, space_order,
22
};
23
24
pub(crate) async fn room_summaries(client: &Client, spaces: &SpaceService) -> Vec<RoomSummary> {
25
    let mut summaries = stream::iter(client.rooms())
26
        .map(|room| async move { room_summary(&room, spaces).await })
27
        .buffer_unordered(16)
28
        .collect::<Vec<_>>()
29
        .await;
30
    summaries.sort_by(|left, right| {
31
        right
32
            .latest_event_timestamp_ms
33
            .cmp(&left.latest_event_timestamp_ms)
34
            .then_with(|| left.name.to_lowercase().cmp(&right.name.to_lowercase()))
35
            .then_with(|| left.room_id.cmp(&right.room_id))
36
    });
37
    summaries
38
}
39
40
async fn room_summary(room: &Room, spaces: &SpaceService) -> RoomSummary {
41
    let name = room
42
        .display_name()
43
        .await
44
        .map(|name| name.to_string())
45
        .unwrap_or_else(|_| room.room_id().to_string());
46
    let custom_notification_mode = room.user_defined_notification_mode().await;
47
    let effective_notification_mode = match custom_notification_mode {
48
        Some(mode) => Some(mode),
49
        None => room.notification_mode().await,
50
    };
51
    let inviter = match room.state() {
52
        RoomState::Invited => room
53
            .invite_details()
54
            .await
55
            .ok()
56
            .map(|invite| invite.inviter_id),
57
        _ => None,
58
    };
59
    let children = if room.is_space() {
60
        space_children(room).await
61
    } else {
62
        Vec::new()
63
    };
64
    RoomSummary {
65
        room_id: room.room_id().to_owned(),
66
        name,
67
        membership: membership(room.state()),
68
        unread_messages: room.num_unread_messages(),
69
        unread_mentions: room.num_unread_mentions(),
70
        unread_notifications: room.num_unread_notifications(),
71
        encrypted: room.encryption_state().is_encrypted(),
72
        space: room.is_space(),
73
        direct: room.is_direct().await.unwrap_or(false),
74
        favourite: room.is_favourite(),
75
        low_priority: room.is_low_priority(),
76
        avatar: room.avatar_url().as_deref().map(MediaId::plain),
77
        latest_event_timestamp_ms: room
78
            .latest_event_timestamp()
79
            .map(|value| value.get().into()),
80
        latest_event: latest_event(room),
81
        notification_mode: effective_notification_mode.map(notification_mode),
82
        notification_mode_custom: custom_notification_mode.is_some(),
83
        topic: room.topic(),
84
        canonical_alias: room.canonical_alias(),
85
        joined_members: room.joined_members_count(),
86
        invited_members: room.invited_members_count(),
87
        heroes: room
88
            .heroes()
89
            .into_iter()
90
            .map(|hero| RoomHero {
91
                user_id: hero.user_id,
92
                display_name: hero.display_name,
93
                avatar: hero.avatar_url.as_deref().map(MediaId::plain),
94
            })
95
            .collect(),
96
        successor: room.successor_room().map(|successor| successor.room_id),
97
        inviter,
98
        parent_spaces: spaces
99
            .joined_parents_of_child(room.room_id())
100
            .await
101
            .into_iter()
102
            .map(|parent| parent.room_id)
103
            .collect(),
104
        children,
105
        space_order: if room.is_space() {
106
            stored_space_order(room).await
107
        } else {
108
            None
109
        },
110
        can_edit_packs: super::can_edit_packs(room).await,
111
    }
112
}
113
114
/// None when unset or when another client wrote something msc3230 doesn't allow
115
pub(crate) async fn stored_space_order(room: &Room) -> Option<String> {
116
    room.account_data(space_order::EVENT_TYPE.into())
117
        .await
118
        .ok()??
119
        .get_field::<Raw<JsonObject>>("content")
120
        .ok()??
121
        .get_field::<String>("order")
122
        .ok()?
123
        .filter(|order| space_order::valid(order))
124
}
125
126
const fn membership(state: RoomState) -> RoomMembership {
127
    match state {
128
        RoomState::Joined => RoomMembership::Joined,
129
        RoomState::Invited => RoomMembership::Invited,
130
        RoomState::Left => RoomMembership::Left,
131
        RoomState::Knocked => RoomMembership::Knocked,
132
        RoomState::Banned => RoomMembership::Banned,
133
    }
134
}
135
136
/// what the space itself lists, straight from local state. redacted children don't count
137
async fn space_children(room: &Room) -> Vec<OwnedRoomId> {
138
    let Ok(events) = room
139
        .get_state_events_static::<SpaceChildEventContent>()
140
        .await
141
    else {
142
        return Vec::new();
143
    };
144
    events
145
        .into_iter()
146
        .filter_map(|event| match event.deserialize().ok()? {
147
            SyncOrStrippedState::Sync(SyncStateEvent::Original(event)) => Some(event.state_key),
148
            SyncOrStrippedState::Stripped(event) => Some(event.state_key),
149
            SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_)) => None,
150
        })
151
        .collect()
152
}
153
154
fn latest_event(room: &Room) -> Option<LatestEvent> {
155
    let own = || Some(room.own_user_id().to_owned());
156
    let local = |local: &matrix_sdk::latest_events::LocalLatestEventValue| {
157
        let preview = match local.content.deserialize() {
158
            Ok(AnyMessageLikeEventContent::RoomMessage(content)) => preview(&content),
159
            _ => (None, None),
160
        };
161
        (preview, Some(local.timestamp))
162
    };
163
    let (sender, (kind, body), timestamp, send_state) = match room.latest_event() {
164
        LatestEventValue::None => return None,
165
        LatestEventValue::Remote(event) => {
166
            let preview = match event.raw().deserialize() {
167
                Ok(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
168
                    message,
169
                ))) => message
170
                    .as_original()
171
                    .map_or((None, None), |message| preview(&message.content)),
172
                _ => (None, None),
173
            };
174
            (event.sender(), preview, event.timestamp(), SendState::Sent)
175
        }
176
        LatestEventValue::RemoteInvite {
177
            timestamp, inviter, ..
178
        } => (inviter, (None, None), Some(timestamp), SendState::Sent),
179
        LatestEventValue::LocalIsSending(value) => {
180
            let (preview, timestamp) = local(&value);
181
            (own(), preview, timestamp, SendState::Sending)
182
        }
183
        LatestEventValue::LocalHasBeenSent { value, .. } => {
184
            let (preview, timestamp) = local(&value);
185
            (own(), preview, timestamp, SendState::Sent)
186
        }
187
        // the sdk won't say whether it'll retry, so assume the user has to
188
        LatestEventValue::LocalCannotBeSent(value) => {
189
            let (preview, timestamp) = local(&value);
190
            (
191
                own(),
192
                preview,
193
                timestamp,
194
                SendState::Failed { will_retry: false },
195
            )
196
        }
197
    };
198
    Some(LatestEvent {
199
        sender,
200
        kind,
201
        body,
202
        timestamp_ms: timestamp.map(|timestamp| timestamp.get().into()),
203
        send_state,
204
    })
205
}
206
207
/// the latest event of an edited message is the edit, whose own body is the `* fallback`
208
fn preview(content: &RoomMessageEventContent) -> (Option<MessageKind>, Option<String>) {
209
    let msgtype = match &content.relates_to {
210
        Some(Relation::Replacement(replacement)) => &replacement.new_content.msgtype,
211
        _ => &content.msgtype,
212
    };
213
    (Some(message_kind(msgtype)), Some(msgtype.body().to_owned()))
214
}
215
216
pub(crate) fn space_child(room: SpaceRoom) -> SpaceChild {
217
    SpaceChild {
218
        name: room.display_name,
219
        room_id: room.room_id,
220
        topic: room.topic,
221
        avatar: room.avatar_url.as_deref().map(MediaId::plain),
222
        canonical_alias: room.canonical_alias,
223
        joined_members: room.num_joined_members,
224
        space: room.room_type == Some(ruma::room::RoomType::Space),
225
        children_count: room.children_count,
226
        membership: room.state.map(membership),
227
        via: room.via,
228
        suggested: room.suggested,
229
        world_readable: room.world_readable,
230
        guest_can_join: room.guest_can_join,
231
    }
232
}
233
234
pub(crate) const fn notification_mode(
235
    mode: matrix_sdk::notification_settings::RoomNotificationMode,
236
) -> RoomNotificationMode {
237
    match mode {
238
        matrix_sdk::notification_settings::RoomNotificationMode::AllMessages => {
239
            RoomNotificationMode::AllMessages
240
        }
241
        matrix_sdk::notification_settings::RoomNotificationMode::MentionsAndKeywordsOnly => {
242
            RoomNotificationMode::MentionsAndKeywordsOnly
243
        }
244
        matrix_sdk::notification_settings::RoomNotificationMode::Mute => RoomNotificationMode::Mute,
245
    }
246
}