atagen

macltok

public
Star0
Watch0
31ce2453c82f470aa8b5d3861ab0eec4273afc5d
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/normalise/timeline.rsRust302 lines10.6 KB
1
use std::sync::Arc;
2
3
use matrix_sdk_ui::{
4
    eyeball_im::VectorDiff,
5
    timeline::{
6
        EventSendState, MsgLikeKind, TimelineDetails, TimelineEventItemId,
7
        TimelineItem as SdkTimelineItem, TimelineItemContent, TimelineItemKind,
8
        VirtualTimelineItem,
9
    },
10
};
11
use ruma::{
12
    UserId,
13
    events::room::message::{MessageFormat, MessageType},
14
};
15
16
use crate::{
17
    MessageMentions, Reaction, ReplyReference, SendState, ThreadSummary, TimelineDiff,
18
    TimelineItem, TimelineItemId, TimelineMessage,
19
};
20
21
use super::media::{
22
    MediaRegistration, MediaScope, media_descriptor_for_sticker, media_from_message,
23
    media_identity, media_registration_for_item,
24
};
25
26
pub(crate) struct NormalisedTimeline {
27
    pub(crate) items: Vec<TimelineItem>,
28
    pub(crate) media: Vec<MediaRegistration>,
29
}
30
31
pub(crate) fn normalise_timeline<'a>(
32
    items: impl Iterator<Item = &'a Arc<SdkTimelineItem>>,
33
    current_user_id: Option<&UserId>,
34
    scope: &MediaScope,
35
) -> NormalisedTimeline {
36
    let mut normalised = NormalisedTimeline {
37
        items: Vec::new(),
38
        media: Vec::new(),
39
    };
40
    for item in items {
41
        if let Some(media) = media_registration_for_item(item, scope) {
42
            normalised.media.push(media);
43
        }
44
        normalised
45
            .items
46
            .push(normalise_timeline_item(item, current_user_id, scope));
47
    }
48
    normalised
49
}
50
51
pub(crate) fn normalise_timeline_diffs(
52
    diffs: Vec<VectorDiff<Arc<SdkTimelineItem>>>,
53
    current_user_id: Option<&UserId>,
54
    scope: &MediaScope,
55
) -> NormalisedTimelineDiffs {
56
    let mut media = Vec::new();
57
    let diffs = diffs
58
        .into_iter()
59
        .map(|diff| {
60
            let diff = diff.map(|item| {
61
                if let Some(registration) = media_registration_for_item(&item, scope) {
62
                    media.push(registration);
63
                }
64
                normalise_timeline_item(&item, current_user_id, scope)
65
            });
66
            match diff {
67
                VectorDiff::Append { values } => TimelineDiff::Append {
68
                    values: values.into_iter().collect(),
69
                },
70
                VectorDiff::Clear => TimelineDiff::Clear,
71
                VectorDiff::PushFront { value } => TimelineDiff::PushFront { value },
72
                VectorDiff::PushBack { value } => TimelineDiff::PushBack { value },
73
                VectorDiff::PopFront => TimelineDiff::PopFront,
74
                VectorDiff::PopBack => TimelineDiff::PopBack,
75
                VectorDiff::Insert { index, value } => TimelineDiff::Insert { index, value },
76
                VectorDiff::Set { index, value } => TimelineDiff::Set { index, value },
77
                VectorDiff::Remove { index } => TimelineDiff::Remove { index },
78
                VectorDiff::Truncate { length } => TimelineDiff::Truncate { length },
79
                VectorDiff::Reset { values } => TimelineDiff::Reset {
80
                    values: values.into_iter().collect(),
81
                },
82
            }
83
        })
84
        .collect();
85
    NormalisedTimelineDiffs { diffs, media }
86
}
87
88
pub(crate) struct NormalisedTimelineDiffs {
89
    pub(crate) diffs: Vec<TimelineDiff>,
90
    pub(crate) media: Vec<MediaRegistration>,
91
}
92
93
fn normalise_timeline_item(
94
    item: &SdkTimelineItem,
95
    current_user_id: Option<&UserId>,
96
    scope: &MediaScope,
97
) -> TimelineItem {
98
    match item.kind() {
99
        TimelineItemKind::Event(event) => TimelineItem::Message(Box::new(timeline_message(
100
            item,
101
            event,
102
            current_user_id,
103
            scope,
104
        ))),
105
        TimelineItemKind::Virtual(VirtualTimelineItem::DateDivider(timestamp)) => {
106
            TimelineItem::DayDivider {
107
                timestamp_ms: timestamp.get().into(),
108
            }
109
        }
110
        TimelineItemKind::Virtual(VirtualTimelineItem::ReadMarker) => TimelineItem::ReadMarker,
111
        TimelineItemKind::Virtual(VirtualTimelineItem::TimelineStart) => {
112
            TimelineItem::TimelineStart
113
        }
114
    }
115
}
116
117
fn timeline_message(
118
    item: &SdkTimelineItem,
119
    event: &matrix_sdk_ui::timeline::EventTimelineItem,
120
    current_user_id: Option<&UserId>,
121
    scope: &MediaScope,
122
) -> TimelineMessage {
123
    let (body, formatted_body, mentions, edited, unable_to_decrypt, media) = match event.content() {
124
        TimelineItemContent::MsgLike(content) => match &content.kind {
125
            MsgLikeKind::Message(message) => (
126
                message.body().to_owned(),
127
                formatted_body(message.msgtype()),
128
                message.mentions().map(message_mentions),
129
                message.is_edited(),
130
                false,
131
                media_from_message(&media_identity(item, scope), message.msgtype()),
132
            ),
133
            MsgLikeKind::Sticker(sticker) => {
134
                let content = sticker.content();
135
                (
136
                    content.body.clone(),
137
                    None,
138
                    None,
139
                    false,
140
                    false,
141
                    Some(media_descriptor_for_sticker(
142
                        &media_identity(item, scope),
143
                        content,
144
                    )),
145
                )
146
            }
147
            MsgLikeKind::Redacted => ("<message redacted>".into(), None, None, false, false, None),
148
            MsgLikeKind::UnableToDecrypt(_) => (
149
                "<unable to decrypt message>".into(),
150
                None,
151
                None,
152
                false,
153
                true,
154
                None,
155
            ),
156
            MsgLikeKind::Poll(_) => ("[poll]".into(), None, None, false, false, None),
157
            MsgLikeKind::LiveLocation(_) => {
158
                ("[live location]".into(), None, None, false, false, None)
159
            }
160
            MsgLikeKind::Other(other) => (
161
                format!("[{}]", other.event_type()),
162
                None,
163
                None,
164
                false,
165
                false,
166
                None,
167
            ),
168
        },
169
        other => (
170
            format!(
171
                "[{}]",
172
                other.event_type_str().unwrap_or_else(|| "event".into())
173
            ),
174
            None,
175
            None,
176
            false,
177
            false,
178
            None,
179
        ),
180
    };
181
    let reply_to = match event.content() {
182
        TimelineItemContent::MsgLike(content) => content.in_reply_to.as_ref().map(|reply| {
183
            let event_id = reply.event_id.to_owned();
184
            let embedded_sender = match &reply.event {
185
                TimelineDetails::Ready(event) => Some((
186
                    event.sender.clone(),
187
                    sender_name(&event.sender, &event.sender_profile),
188
                )),
189
                _ => None,
190
            };
191
            ReplyReference {
192
                event_id,
193
                sender_id: embedded_sender.as_ref().map(|(id, _)| id.clone()),
194
                sender_name: embedded_sender.map(|(_, name)| name),
195
            }
196
        }),
197
        _ => None,
198
    };
199
    let (thread_root, thread_summary) = match event.content() {
200
        TimelineItemContent::MsgLike(content) => (
201
            content.thread_root.clone(),
202
            content.thread_summary.as_ref().map(|summary| {
203
                let latest_event = match &summary.latest_event {
204
                    TimelineDetails::Ready(event) => Some(event.as_ref()),
205
                    _ => None,
206
                };
207
                ThreadSummary {
208
                    reply_count: summary.num_replies,
209
                    latest_event_id: latest_event.and_then(|event| match &event.identifier {
210
                        TimelineEventItemId::EventId(event_id) => Some(event_id.clone()),
211
                        TimelineEventItemId::TransactionId(_) => None,
212
                    }),
213
                    latest_event_timestamp_ms: latest_event
214
                        .map(|event| event.timestamp.get().into()),
215
                }
216
            }),
217
        ),
218
        _ => (None, None),
219
    };
220
    let reactions = event
221
        .content()
222
        .reactions()
223
        .into_iter()
224
        .flat_map(|reactions| reactions.iter())
225
        .map(|(key, senders)| Reaction {
226
            key: key.clone(),
227
            count: senders.len(),
228
            own: current_user_id.is_some_and(|user_id| senders.contains_key(user_id)),
229
            senders: senders.keys().cloned().collect(),
230
        })
231
        .collect();
232
    let mut read_by: Vec<_> = event
233
        .read_receipts()
234
        .keys()
235
        .filter(|user_id| *user_id != event.sender())
236
        .cloned()
237
        .collect();
238
    read_by.sort();
239
240
    TimelineMessage {
241
        item_id: TimelineItemId::new(item.unique_id().0.clone()),
242
        event_id: event.event_id().map(ToOwned::to_owned),
243
        transaction_id: event.transaction_id().map(ToOwned::to_owned),
244
        sender_id: event.sender().to_owned(),
245
        sender_name: sender_name(event.sender(), event.sender_profile()),
246
        body,
247
        formatted_body,
248
        mentions,
249
        thread_root,
250
        thread_summary,
251
        timestamp_ms: event.timestamp().get().into(),
252
        own: event.is_own(),
253
        send_state: match event.send_state() {
254
            None | Some(EventSendState::Sent { .. }) => SendState::Sent,
255
            Some(EventSendState::NotSentYet { progress: Some(_) }) => SendState::Sending,
256
            Some(EventSendState::NotSentYet { progress: None }) => SendState::Queued,
257
            Some(EventSendState::SendingFailed { is_recoverable, .. }) => SendState::Failed {
258
                retriable: *is_recoverable,
259
            },
260
        },
261
        edited,
262
        unable_to_decrypt,
263
        media,
264
        reply_to,
265
        reactions,
266
        read_by,
267
    }
268
}
269
270
fn sender_name(
271
    sender: &UserId,
272
    sender_profile: &TimelineDetails<matrix_sdk_ui::timeline::Profile>,
273
) -> String {
274
    match sender_profile {
275
        TimelineDetails::Ready(profile) => profile
276
            .display_name
277
            .clone()
278
            .unwrap_or_else(|| sender.to_string()),
279
        _ => sender.to_string(),
280
    }
281
}
282
283
fn formatted_body(message: &MessageType) -> Option<String> {
284
    let formatted = match message {
285
        MessageType::Text(content) => content.formatted.as_ref(),
286
        MessageType::Emote(content) => content.formatted.as_ref(),
287
        MessageType::Notice(content) => content.formatted.as_ref(),
288
        MessageType::Audio(content) => content.formatted_caption(),
289
        MessageType::File(content) => content.formatted_caption(),
290
        MessageType::Image(content) => content.formatted_caption(),
291
        MessageType::Video(content) => content.formatted_caption(),
292
        _ => None,
293
    }?;
294
    (formatted.format == MessageFormat::Html).then(|| formatted.body.clone())
295
}
296
297
fn message_mentions(mentions: &ruma::events::Mentions) -> MessageMentions {
298
    MessageMentions {
299
        user_ids: mentions.user_ids.iter().cloned().collect(),
300
        room: mentions.room,
301
    }
302
}