atagen

macltok

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