atagen

macltok

public
Star0
Watch0
06c73391cbaec25f3bfaacf44d54a9ac657ec4e9
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/normalise/observed.rsRust271 lines10.6 KB
1
use ruma::{
2
    OwnedEventId, UserId,
3
    events::{
4
        poll::{start::PollStartEventContent, unstable_start::UnstablePollStartEventContent},
5
        room::message::{
6
            MessageType, Relation, RoomMessageEventContent, RoomMessageEventContentWithoutRelation,
7
        },
8
        sticker::StickerEventContent,
9
    },
10
};
11
use serde_json::Value;
12
13
use super::{
14
    link_preview::sender_previews,
15
    media::{
16
        MediaRegistration, media_descriptor_for_sticker, media_registration,
17
        media_sticker_registration,
18
    },
19
    timeline::formatted_body,
20
};
21
use crate::{
22
    Message, MessageMentions, ReplyDetails, ReplyReference, SendState, Sender, TimelineContent,
23
    TimelineEvent, TimelineItem, TimelineItemId, UtdCause,
24
};
25
26
pub(crate) fn normalise_observed(
27
    value: &Value,
28
    current_user_id: Option<&UserId>,
29
    identity: &str,
30
) -> Option<(TimelineItem, Vec<MediaRegistration>)> {
31
    let event_id: OwnedEventId = serde_json::from_value(value.get("event_id")?.clone()).ok()?;
32
    let sender_id: ruma::OwnedUserId = serde_json::from_value(value.get("sender")?.clone()).ok()?;
33
    let timestamp_ms = value.get("origin_server_ts")?.as_u64()?;
34
    let event_type = value.get("type")?.as_str()?.to_owned();
35
    let own = current_user_id.is_some_and(|user_id| user_id.as_str() == sender_id.as_str());
36
    let redacted = value
37
        .get("unsigned")
38
        .and_then(|unsigned| unsigned.get("redacted_because"))
39
        .is_some();
40
    let mut media = Vec::new();
41
    let (content, reply_to, thread_root) = if redacted {
42
        (TimelineContent::Redacted, None, None)
43
    } else if event_type == "m.room.message" {
44
        match value
45
            .get("content")
46
            .cloned()
47
            .and_then(|content| serde_json::from_value::<RoomMessageEventContent>(content).ok())
48
        {
49
            Some(mut message) => {
50
                let (reply_to, thread_root) = relation_refs(message.relates_to.as_ref());
51
                let relation_replacement = match &message.relates_to {
52
                    Some(Relation::Replacement(replacement)) => {
53
                        Some(replacement.new_content.clone())
54
                    }
55
                    _ => None,
56
                };
57
                let bundled_replacement = value
58
                    .get("unsigned")
59
                    .and_then(|unsigned| unsigned.get("m.relations"))
60
                    .and_then(|relations| relations.get("m.replace"))
61
                    .and_then(|edit| {
62
                        let edit_sender = edit.get("sender")?.as_str()?;
63
                        if edit_sender != sender_id.as_str() {
64
                            return None;
65
                        }
66
                        edit.get("content")?.get("m.new_content").cloned()
67
                    })
68
                    .and_then(|content| {
69
                        serde_json::from_value::<RoomMessageEventContentWithoutRelation>(content)
70
                            .ok()
71
                    });
72
                let edited = relation_replacement.is_some()
73
                    || bundled_replacement.is_some()
74
                    || value
75
                        .get("unsigned")
76
                        .and_then(|unsigned| unsigned.get("macltok_edited"))
77
                        .and_then(Value::as_bool)
78
                        .unwrap_or(false);
79
                if let Some(replacement) = relation_replacement.or(bundled_replacement) {
80
                    message.apply_replacement(replacement);
81
                }
82
                let message_type = &message.msgtype;
83
                media.extend(media_registration(identity, message_type));
84
                let previews = sender_previews(Some(identity), message_type);
85
                media.extend(
86
                    previews
87
                        .iter()
88
                        .filter_map(|(_, registration)| registration.clone()),
89
                );
90
                (
91
                    TimelineContent::Message(Message {
92
                        kind: super::message_kind(message_type),
93
                        body: message.body().to_owned(),
94
                        geo_uri: match message_type {
95
                            MessageType::Location(location) => Some(location.geo_uri.clone()),
96
                            _ => None,
97
                        },
98
                        formatted_body: formatted_body(message_type),
99
                        mentions: message.mentions.as_ref().map(|mentions| MessageMentions {
100
                            user_ids: mentions.user_ids.iter().cloned().collect(),
101
                            room: mentions.room,
102
                        }),
103
                        edited,
104
                        media: super::media::media_from_message(identity, message_type),
105
                        link_previews: previews.into_iter().map(|(preview, _)| preview).collect(),
106
                    }),
107
                    reply_to,
108
                    thread_root,
109
                )
110
            }
111
            None => (TimelineContent::Other { event_type }, None, None),
112
        }
113
    } else if event_type == "m.room.encrypted" {
114
        (
115
            TimelineContent::UnableToDecrypt(UtdCause::Unknown),
116
            None,
117
            None,
118
        )
119
    } else if event_type == "m.sticker" {
120
        match value
121
            .get("content")
122
            .cloned()
123
            .and_then(|content| serde_json::from_value::<StickerEventContent>(content).ok())
124
        {
125
            Some(sticker) => {
126
                let (reply_to, thread_root) = relation_refs(sticker.relates_to.as_ref());
127
                media.extend(media_sticker_registration(identity, &sticker));
128
                (
129
                    TimelineContent::Sticker {
130
                        body: sticker.body.clone(),
131
                        media: media_descriptor_for_sticker(identity, &sticker),
132
                    },
133
                    reply_to,
134
                    thread_root,
135
                )
136
            }
137
            None => (TimelineContent::Other { event_type }, None, None),
138
        }
139
    } else if event_type == "m.poll.start" || event_type == "org.matrix.msc3381.poll.start" {
140
        let poll_content = value.get("content");
141
        let question = poll_content.and_then(|content| poll_question(content, &event_type));
142
        match question {
143
            Some(question) => {
144
                let (reply_to, thread_root) =
145
                    poll_content.map(raw_relation_refs).unwrap_or_default();
146
                (TimelineContent::Poll { question }, reply_to, thread_root)
147
            }
148
            None => (TimelineContent::Other { event_type }, None, None),
149
        }
150
    } else if let Some(state_key) = value.get("state_key").and_then(Value::as_str) {
151
        (
152
            TimelineContent::OtherState {
153
                event_type,
154
                state_key: state_key.to_owned(),
155
            },
156
            None,
157
            None,
158
        )
159
    } else {
160
        (TimelineContent::Other { event_type }, None, None)
161
    };
162
    let event = TimelineEvent {
163
        item_id: TimelineItemId::new(identity.to_owned()),
164
        event_id: Some(event_id),
165
        transaction_id: None,
166
        sender: Sender {
167
            user_id: sender_id,
168
            display_name: None,
169
            avatar: None,
170
            name_ambiguous: false,
171
        },
172
        timestamp_ms,
173
        own,
174
        send_state: SendState::Sent,
175
        content,
176
        reply_to: reply_to.map(|event_id| ReplyReference {
177
            event_id,
178
            details: ReplyDetails::Pending,
179
        }),
180
        thread_root,
181
        thread_summary: None,
182
        reactions: Vec::new(),
183
        read_by: Vec::new(),
184
        editable: false,
185
        highlight: false,
186
    };
187
    Some((TimelineItem::Event(Box::new(event)), media))
188
}
189
190
fn relation_refs<C>(
191
    relation: Option<&Relation<C>>,
192
) -> (Option<OwnedEventId>, Option<OwnedEventId>) {
193
    match relation {
194
        Some(Relation::Reply(reply)) => (Some(reply.in_reply_to.event_id.clone()), None),
195
        Some(Relation::Thread(thread)) => (
196
            thread
197
                .in_reply_to
198
                .as_ref()
199
                .map(|reply| reply.event_id.clone()),
200
            Some(thread.event_id.clone()),
201
        ),
202
        _ => (None, None),
203
    }
204
}
205
206
fn raw_relation_refs(content: &Value) -> (Option<OwnedEventId>, Option<OwnedEventId>) {
207
    let Some(relation) = content.get("m.relates_to") else {
208
        return (None, None);
209
    };
210
    let reply_to = relation
211
        .get("m.in_reply_to")
212
        .and_then(|reply| reply.get("event_id"))
213
        .and_then(Value::as_str)
214
        .and_then(|event_id| event_id.parse().ok());
215
    let thread_root = (relation.get("rel_type").and_then(Value::as_str) == Some("m.thread"))
216
        .then(|| relation.get("event_id").and_then(Value::as_str))
217
        .flatten()
218
        .and_then(|event_id| event_id.parse().ok());
219
    (reply_to, thread_root)
220
}
221
222
fn poll_question(content: &Value, event_type: &str) -> Option<String> {
223
    let typed_question = match event_type {
224
        "m.poll.start" => serde_json::from_value::<PollStartEventContent>(content.clone())
225
            .ok()
226
            .and_then(|poll| poll.poll.question.text.find_plain().map(str::to_owned)),
227
        "org.matrix.msc3381.poll.start" => {
228
            serde_json::from_value::<UnstablePollStartEventContent>(content.clone())
229
                .ok()
230
                .map(|poll| poll.poll_start().question.text.clone())
231
        }
232
        _ => None,
233
    };
234
    typed_question
235
        .or_else(|| {
236
            content
237
                .get("m.poll")
238
                .and_then(|poll| poll.get("question"))
239
                .and_then(|question| question.get("m.text"))
240
                .and_then(generic_text)
241
        })
242
        .or_else(|| {
243
            content
244
                .get("org.matrix.msc3381.poll.start")
245
                .and_then(|poll| poll.get("question"))
246
                .and_then(|question| question.get("org.matrix.msc1767.text"))
247
                .and_then(generic_text)
248
        })
249
        .or_else(|| {
250
            ["org.matrix.msc1767.text", "m.text", "question", "body"]
251
                .iter()
252
                .find_map(|key| content.get(*key).and_then(generic_text))
253
        })
254
}
255
256
fn generic_text(value: &Value) -> Option<String> {
257
    match value {
258
        Value::String(text) => Some(text.clone()),
259
        Value::Array(representations) => representations
260
            .iter()
261
            .find(|representation| {
262
                representation.get("mimetype").and_then(Value::as_str) == Some("text/plain")
263
            })
264
            .or_else(|| representations.first())
265
            .and_then(|representation| representation.get("body"))
266
            .and_then(Value::as_str)
267
            .map(str::to_owned),
268
        Value::Object(_) => value.get("body").and_then(Value::as_str).map(str::to_owned),
269
        _ => None,
270
    }
271
}