atagen

macltok

public
Star0
Watch0
6a5963d5bd6374776bde92c39bea7d2cceceee1d
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git

Blame: src/normalise/observed.rs

cbb715e11963atagen2026-10-24 23:24 UTC1
use ruma::{
cbb715e11963atagen2026-10-24 23:24 UTC2
    OwnedEventId, UserId,
cbb715e11963atagen2026-10-24 23:24 UTC3
    events::{
cbb715e11963atagen2026-10-24 23:24 UTC4
        poll::{start::PollStartEventContent, unstable_start::UnstablePollStartEventContent},
068bfa92dd36atagen2026-10-25 13:53 UTC5
        room::{
068bfa92dd36atagen2026-10-25 13:53 UTC6
            member::{MembershipChange as RumaMembershipChange, OriginalSyncRoomMemberEvent},
068bfa92dd36atagen2026-10-25 13:53 UTC7
            message::{
068bfa92dd36atagen2026-10-25 13:53 UTC8
                MessageType, Relation, RoomMessageEventContent,
068bfa92dd36atagen2026-10-25 13:53 UTC9
                RoomMessageEventContentWithoutRelation,
068bfa92dd36atagen2026-10-25 13:53 UTC10
            },
cbb715e11963atagen2026-10-24 23:24 UTC11
        },
cbb715e11963atagen2026-10-24 23:24 UTC12
        sticker::StickerEventContent,
cbb715e11963atagen2026-10-24 23:24 UTC13
    },
cbb715e11963atagen2026-10-24 23:24 UTC14
};
cbb715e11963atagen2026-10-24 23:24 UTC15
use serde_json::Value;
cbb715e11963atagen2026-10-24 23:24 UTC16
cbb715e11963atagen2026-10-24 23:24 UTC17
use super::{
cbb715e11963atagen2026-10-24 23:24 UTC18
    link_preview::sender_previews,
cbb715e11963atagen2026-10-24 23:24 UTC19
    media::{
cbb715e11963atagen2026-10-24 23:24 UTC20
        MediaRegistration, media_descriptor_for_sticker, media_registration,
cbb715e11963atagen2026-10-24 23:24 UTC21
        media_sticker_registration,
cbb715e11963atagen2026-10-24 23:24 UTC22
    },
cbb715e11963atagen2026-10-24 23:24 UTC23
    timeline::formatted_body,
cbb715e11963atagen2026-10-24 23:24 UTC24
};
cbb715e11963atagen2026-10-24 23:24 UTC25
use crate::{
068bfa92dd36atagen2026-10-25 13:53 UTC26
    Change, MediaId, MembershipChange, Message, MessageMentions, ReplyDetails, ReplyReference,
068bfa92dd36atagen2026-10-25 13:53 UTC27
    SendState, Sender, TimelineContent, TimelineEvent, TimelineItem, TimelineItemId, UtdCause,
cbb715e11963atagen2026-10-24 23:24 UTC28
};
cbb715e11963atagen2026-10-24 23:24 UTC29
cbb715e11963atagen2026-10-24 23:24 UTC30
pub(crate) fn normalise_observed(
cbb715e11963atagen2026-10-24 23:24 UTC31
    value: &Value,
cbb715e11963atagen2026-10-24 23:24 UTC32
    current_user_id: Option<&UserId>,
cbb715e11963atagen2026-10-24 23:24 UTC33
    identity: &str,
cbb715e11963atagen2026-10-24 23:24 UTC34
) -> Option<(TimelineItem, Vec<MediaRegistration>)> {
cbb715e11963atagen2026-10-24 23:24 UTC35
    let event_id: OwnedEventId = serde_json::from_value(value.get("event_id")?.clone()).ok()?;
cbb715e11963atagen2026-10-24 23:24 UTC36
    let sender_id: ruma::OwnedUserId = serde_json::from_value(value.get("sender")?.clone()).ok()?;
cbb715e11963atagen2026-10-24 23:24 UTC37
    let timestamp_ms = value.get("origin_server_ts")?.as_u64()?;
cbb715e11963atagen2026-10-24 23:24 UTC38
    let event_type = value.get("type")?.as_str()?.to_owned();
cbb715e11963atagen2026-10-24 23:24 UTC39
    let own = current_user_id.is_some_and(|user_id| user_id.as_str() == sender_id.as_str());
cbb715e11963atagen2026-10-24 23:24 UTC40
    let redacted = value
cbb715e11963atagen2026-10-24 23:24 UTC41
        .get("unsigned")
cbb715e11963atagen2026-10-24 23:24 UTC42
        .and_then(|unsigned| unsigned.get("redacted_because"))
cbb715e11963atagen2026-10-24 23:24 UTC43
        .is_some();
cbb715e11963atagen2026-10-24 23:24 UTC44
    let mut media = Vec::new();
cbb715e11963atagen2026-10-24 23:24 UTC45
    let (content, reply_to, thread_root) = if redacted {
cbb715e11963atagen2026-10-24 23:24 UTC46
        (TimelineContent::Redacted, None, None)
cbb715e11963atagen2026-10-24 23:24 UTC47
    } else if event_type == "m.room.message" {
cbb715e11963atagen2026-10-24 23:24 UTC48
        match value
cbb715e11963atagen2026-10-24 23:24 UTC49
            .get("content")
cbb715e11963atagen2026-10-24 23:24 UTC50
            .cloned()
cbb715e11963atagen2026-10-24 23:24 UTC51
            .and_then(|content| serde_json::from_value::<RoomMessageEventContent>(content).ok())
cbb715e11963atagen2026-10-24 23:24 UTC52
        {
cbb715e11963atagen2026-10-24 23:24 UTC53
            Some(mut message) => {
cbb715e11963atagen2026-10-24 23:24 UTC54
                let (reply_to, thread_root) = relation_refs(message.relates_to.as_ref());
cbb715e11963atagen2026-10-24 23:24 UTC55
                let relation_replacement = match &message.relates_to {
cbb715e11963atagen2026-10-24 23:24 UTC56
                    Some(Relation::Replacement(replacement)) => {
cbb715e11963atagen2026-10-24 23:24 UTC57
                        Some(replacement.new_content.clone())
cbb715e11963atagen2026-10-24 23:24 UTC58
                    }
cbb715e11963atagen2026-10-24 23:24 UTC59
                    _ => None,
cbb715e11963atagen2026-10-24 23:24 UTC60
                };
cbb715e11963atagen2026-10-24 23:24 UTC61
                let bundled_replacement = value
cbb715e11963atagen2026-10-24 23:24 UTC62
                    .get("unsigned")
cbb715e11963atagen2026-10-24 23:24 UTC63
                    .and_then(|unsigned| unsigned.get("m.relations"))
cbb715e11963atagen2026-10-24 23:24 UTC64
                    .and_then(|relations| relations.get("m.replace"))
cbb715e11963atagen2026-10-24 23:24 UTC65
                    .and_then(|edit| {
cbb715e11963atagen2026-10-24 23:24 UTC66
                        let edit_sender = edit.get("sender")?.as_str()?;
cbb715e11963atagen2026-10-24 23:24 UTC67
                        if edit_sender != sender_id.as_str() {
cbb715e11963atagen2026-10-24 23:24 UTC68
                            return None;
cbb715e11963atagen2026-10-24 23:24 UTC69
                        }
cbb715e11963atagen2026-10-24 23:24 UTC70
                        edit.get("content")?.get("m.new_content").cloned()
cbb715e11963atagen2026-10-24 23:24 UTC71
                    })
cbb715e11963atagen2026-10-24 23:24 UTC72
                    .and_then(|content| {
cbb715e11963atagen2026-10-24 23:24 UTC73
                        serde_json::from_value::<RoomMessageEventContentWithoutRelation>(content)
cbb715e11963atagen2026-10-24 23:24 UTC74
                            .ok()
cbb715e11963atagen2026-10-24 23:24 UTC75
                    });
cbb715e11963atagen2026-10-24 23:24 UTC76
                let edited = relation_replacement.is_some()
cbb715e11963atagen2026-10-24 23:24 UTC77
                    || bundled_replacement.is_some()
cbb715e11963atagen2026-10-24 23:24 UTC78
                    || value
cbb715e11963atagen2026-10-24 23:24 UTC79
                        .get("unsigned")
cbb715e11963atagen2026-10-24 23:24 UTC80
                        .and_then(|unsigned| unsigned.get("macltok_edited"))
cbb715e11963atagen2026-10-24 23:24 UTC81
                        .and_then(Value::as_bool)
cbb715e11963atagen2026-10-24 23:24 UTC82
                        .unwrap_or(false);
cbb715e11963atagen2026-10-24 23:24 UTC83
                if let Some(replacement) = relation_replacement.or(bundled_replacement) {
cbb715e11963atagen2026-10-24 23:24 UTC84
                    message.apply_replacement(replacement);
cbb715e11963atagen2026-10-24 23:24 UTC85
                }
cbb715e11963atagen2026-10-24 23:24 UTC86
                let message_type = &message.msgtype;
cbb715e11963atagen2026-10-24 23:24 UTC87
                media.extend(media_registration(identity, message_type));
cbb715e11963atagen2026-10-24 23:24 UTC88
                let previews = sender_previews(Some(identity), message_type);
cbb715e11963atagen2026-10-24 23:24 UTC89
                media.extend(
cbb715e11963atagen2026-10-24 23:24 UTC90
                    previews
cbb715e11963atagen2026-10-24 23:24 UTC91
                        .iter()
cbb715e11963atagen2026-10-24 23:24 UTC92
                        .filter_map(|(_, registration)| registration.clone()),
cbb715e11963atagen2026-10-24 23:24 UTC93
                );
cbb715e11963atagen2026-10-24 23:24 UTC94
                (
cbb715e11963atagen2026-10-24 23:24 UTC95
                    TimelineContent::Message(Message {
cbb715e11963atagen2026-10-24 23:24 UTC96
                        kind: super::message_kind(message_type),
cbb715e11963atagen2026-10-24 23:24 UTC97
                        body: message.body().to_owned(),
cbb715e11963atagen2026-10-24 23:24 UTC98
                        geo_uri: match message_type {
cbb715e11963atagen2026-10-24 23:24 UTC99
                            MessageType::Location(location) => Some(location.geo_uri.clone()),
cbb715e11963atagen2026-10-24 23:24 UTC100
                            _ => None,
cbb715e11963atagen2026-10-24 23:24 UTC101
                        },
cbb715e11963atagen2026-10-24 23:24 UTC102
                        formatted_body: formatted_body(message_type),
cbb715e11963atagen2026-10-24 23:24 UTC103
                        mentions: message.mentions.as_ref().map(|mentions| MessageMentions {
cbb715e11963atagen2026-10-24 23:24 UTC104
                            user_ids: mentions.user_ids.iter().cloned().collect(),
cbb715e11963atagen2026-10-24 23:24 UTC105
                            room: mentions.room,
cbb715e11963atagen2026-10-24 23:24 UTC106
                        }),
cbb715e11963atagen2026-10-24 23:24 UTC107
                        edited,
cbb715e11963atagen2026-10-24 23:24 UTC108
                        media: super::media::media_from_message(identity, message_type),
cbb715e11963atagen2026-10-24 23:24 UTC109
                        link_previews: previews.into_iter().map(|(preview, _)| preview).collect(),
cbb715e11963atagen2026-10-24 23:24 UTC110
                    }),
cbb715e11963atagen2026-10-24 23:24 UTC111
                    reply_to,
cbb715e11963atagen2026-10-24 23:24 UTC112
                    thread_root,
cbb715e11963atagen2026-10-24 23:24 UTC113
                )
cbb715e11963atagen2026-10-24 23:24 UTC114
            }
cbb715e11963atagen2026-10-24 23:24 UTC115
            None => (TimelineContent::Other { event_type }, None, None),
cbb715e11963atagen2026-10-24 23:24 UTC116
        }
cbb715e11963atagen2026-10-24 23:24 UTC117
    } else if event_type == "m.room.encrypted" {
cbb715e11963atagen2026-10-24 23:24 UTC118
        (
cbb715e11963atagen2026-10-24 23:24 UTC119
            TimelineContent::UnableToDecrypt(UtdCause::Unknown),
cbb715e11963atagen2026-10-24 23:24 UTC120
            None,
cbb715e11963atagen2026-10-24 23:24 UTC121
            None,
cbb715e11963atagen2026-10-24 23:24 UTC122
        )
cbb715e11963atagen2026-10-24 23:24 UTC123
    } else if event_type == "m.sticker" {
cbb715e11963atagen2026-10-24 23:24 UTC124
        match value
cbb715e11963atagen2026-10-24 23:24 UTC125
            .get("content")
cbb715e11963atagen2026-10-24 23:24 UTC126
            .cloned()
cbb715e11963atagen2026-10-24 23:24 UTC127
            .and_then(|content| serde_json::from_value::<StickerEventContent>(content).ok())
cbb715e11963atagen2026-10-24 23:24 UTC128
        {
cbb715e11963atagen2026-10-24 23:24 UTC129
            Some(sticker) => {
cbb715e11963atagen2026-10-24 23:24 UTC130
                let (reply_to, thread_root) = relation_refs(sticker.relates_to.as_ref());
cbb715e11963atagen2026-10-24 23:24 UTC131
                media.extend(media_sticker_registration(identity, &sticker));
cbb715e11963atagen2026-10-24 23:24 UTC132
                (
cbb715e11963atagen2026-10-24 23:24 UTC133
                    TimelineContent::Sticker {
cbb715e11963atagen2026-10-24 23:24 UTC134
                        body: sticker.body.clone(),
cbb715e11963atagen2026-10-24 23:24 UTC135
                        media: media_descriptor_for_sticker(identity, &sticker),
cbb715e11963atagen2026-10-24 23:24 UTC136
                    },
cbb715e11963atagen2026-10-24 23:24 UTC137
                    reply_to,
cbb715e11963atagen2026-10-24 23:24 UTC138
                    thread_root,
cbb715e11963atagen2026-10-24 23:24 UTC139
                )
cbb715e11963atagen2026-10-24 23:24 UTC140
            }
cbb715e11963atagen2026-10-24 23:24 UTC141
            None => (TimelineContent::Other { event_type }, None, None),
cbb715e11963atagen2026-10-24 23:24 UTC142
        }
cbb715e11963atagen2026-10-24 23:24 UTC143
    } else if event_type == "m.poll.start" || event_type == "org.matrix.msc3381.poll.start" {
cbb715e11963atagen2026-10-24 23:24 UTC144
        let poll_content = value.get("content");
cbb715e11963atagen2026-10-24 23:24 UTC145
        let question = poll_content.and_then(|content| poll_question(content, &event_type));
cbb715e11963atagen2026-10-24 23:24 UTC146
        match question {
cbb715e11963atagen2026-10-24 23:24 UTC147
            Some(question) => {
cbb715e11963atagen2026-10-24 23:24 UTC148
                let (reply_to, thread_root) =
cbb715e11963atagen2026-10-24 23:24 UTC149
                    poll_content.map(raw_relation_refs).unwrap_or_default();
cbb715e11963atagen2026-10-24 23:24 UTC150
                (TimelineContent::Poll { question }, reply_to, thread_root)
cbb715e11963atagen2026-10-24 23:24 UTC151
            }
cbb715e11963atagen2026-10-24 23:24 UTC152
            None => (TimelineContent::Other { event_type }, None, None),
cbb715e11963atagen2026-10-24 23:24 UTC153
        }
cbb715e11963atagen2026-10-24 23:24 UTC154
    } else if let Some(state_key) = value.get("state_key").and_then(Value::as_str) {
068bfa92dd36atagen2026-10-25 13:53 UTC155
        let content = state_content(value, &event_type).unwrap_or(TimelineContent::OtherState {
068bfa92dd36atagen2026-10-25 13:53 UTC156
            event_type,
068bfa92dd36atagen2026-10-25 13:53 UTC157
            state_key: state_key.to_owned(),
068bfa92dd36atagen2026-10-25 13:53 UTC158
        });
068bfa92dd36atagen2026-10-25 13:53 UTC159
        (content, None, None)
cbb715e11963atagen2026-10-24 23:24 UTC160
    } else {
cbb715e11963atagen2026-10-24 23:24 UTC161
        (TimelineContent::Other { event_type }, None, None)
cbb715e11963atagen2026-10-24 23:24 UTC162
    };
cbb715e11963atagen2026-10-24 23:24 UTC163
    let event = TimelineEvent {
cbb715e11963atagen2026-10-24 23:24 UTC164
        item_id: TimelineItemId::new(identity.to_owned()),
cbb715e11963atagen2026-10-24 23:24 UTC165
        event_id: Some(event_id),
cbb715e11963atagen2026-10-24 23:24 UTC166
        transaction_id: None,
cbb715e11963atagen2026-10-24 23:24 UTC167
        sender: Sender {
cbb715e11963atagen2026-10-24 23:24 UTC168
            user_id: sender_id,
cbb715e11963atagen2026-10-24 23:24 UTC169
            display_name: None,
cbb715e11963atagen2026-10-24 23:24 UTC170
            avatar: None,
cbb715e11963atagen2026-10-24 23:24 UTC171
            name_ambiguous: false,
cbb715e11963atagen2026-10-24 23:24 UTC172
        },
cbb715e11963atagen2026-10-24 23:24 UTC173
        timestamp_ms,
cbb715e11963atagen2026-10-24 23:24 UTC174
        own,
cbb715e11963atagen2026-10-24 23:24 UTC175
        send_state: SendState::Sent,
cbb715e11963atagen2026-10-24 23:24 UTC176
        content,
cbb715e11963atagen2026-10-24 23:24 UTC177
        reply_to: reply_to.map(|event_id| ReplyReference {
cbb715e11963atagen2026-10-24 23:24 UTC178
            event_id,
cbb715e11963atagen2026-10-24 23:24 UTC179
            details: ReplyDetails::Pending,
cbb715e11963atagen2026-10-24 23:24 UTC180
        }),
cbb715e11963atagen2026-10-24 23:24 UTC181
        thread_root,
cbb715e11963atagen2026-10-24 23:24 UTC182
        thread_summary: None,
cbb715e11963atagen2026-10-24 23:24 UTC183
        reactions: Vec::new(),
cbb715e11963atagen2026-10-24 23:24 UTC184
        read_by: Vec::new(),
cbb715e11963atagen2026-10-24 23:24 UTC185
        editable: false,
913832a327a8atagen2026-10-25 09:33 UTC186
        highlight: false,
cbb715e11963atagen2026-10-24 23:24 UTC187
    };
cbb715e11963atagen2026-10-24 23:24 UTC188
    Some((TimelineItem::Event(Box::new(event)), media))
cbb715e11963atagen2026-10-24 23:24 UTC189
}
cbb715e11963atagen2026-10-24 23:24 UTC190
cbb715e11963atagen2026-10-24 23:24 UTC191
fn relation_refs<C>(
cbb715e11963atagen2026-10-24 23:24 UTC192
    relation: Option<&Relation<C>>,
cbb715e11963atagen2026-10-24 23:24 UTC193
) -> (Option<OwnedEventId>, Option<OwnedEventId>) {
cbb715e11963atagen2026-10-24 23:24 UTC194
    match relation {
cbb715e11963atagen2026-10-24 23:24 UTC195
        Some(Relation::Reply(reply)) => (Some(reply.in_reply_to.event_id.clone()), None),
cbb715e11963atagen2026-10-24 23:24 UTC196
        Some(Relation::Thread(thread)) => (
cbb715e11963atagen2026-10-24 23:24 UTC197
            thread
cbb715e11963atagen2026-10-24 23:24 UTC198
                .in_reply_to
cbb715e11963atagen2026-10-24 23:24 UTC199
                .as_ref()
cbb715e11963atagen2026-10-24 23:24 UTC200
                .map(|reply| reply.event_id.clone()),
cbb715e11963atagen2026-10-24 23:24 UTC201
            Some(thread.event_id.clone()),
cbb715e11963atagen2026-10-24 23:24 UTC202
        ),
cbb715e11963atagen2026-10-24 23:24 UTC203
        _ => (None, None),
cbb715e11963atagen2026-10-24 23:24 UTC204
    }
cbb715e11963atagen2026-10-24 23:24 UTC205
}
cbb715e11963atagen2026-10-24 23:24 UTC206
cbb715e11963atagen2026-10-24 23:24 UTC207
fn raw_relation_refs(content: &Value) -> (Option<OwnedEventId>, Option<OwnedEventId>) {
cbb715e11963atagen2026-10-24 23:24 UTC208
    let Some(relation) = content.get("m.relates_to") else {
cbb715e11963atagen2026-10-24 23:24 UTC209
        return (None, None);
cbb715e11963atagen2026-10-24 23:24 UTC210
    };
cbb715e11963atagen2026-10-24 23:24 UTC211
    let reply_to = relation
cbb715e11963atagen2026-10-24 23:24 UTC212
        .get("m.in_reply_to")
cbb715e11963atagen2026-10-24 23:24 UTC213
        .and_then(|reply| reply.get("event_id"))
cbb715e11963atagen2026-10-24 23:24 UTC214
        .and_then(Value::as_str)
cbb715e11963atagen2026-10-24 23:24 UTC215
        .and_then(|event_id| event_id.parse().ok());
cbb715e11963atagen2026-10-24 23:24 UTC216
    let thread_root = (relation.get("rel_type").and_then(Value::as_str) == Some("m.thread"))
cbb715e11963atagen2026-10-24 23:24 UTC217
        .then(|| relation.get("event_id").and_then(Value::as_str))
cbb715e11963atagen2026-10-24 23:24 UTC218
        .flatten()
cbb715e11963atagen2026-10-24 23:24 UTC219
        .and_then(|event_id| event_id.parse().ok());
cbb715e11963atagen2026-10-24 23:24 UTC220
    (reply_to, thread_root)
cbb715e11963atagen2026-10-24 23:24 UTC221
}
cbb715e11963atagen2026-10-24 23:24 UTC222
c9effeac326aatagen2026-10-25 10:32 UTC223
pub(crate) fn poll_question(content: &Value, event_type: &str) -> Option<String> {
cbb715e11963atagen2026-10-24 23:24 UTC224
    let typed_question = match event_type {
cbb715e11963atagen2026-10-24 23:24 UTC225
        "m.poll.start" => serde_json::from_value::<PollStartEventContent>(content.clone())
cbb715e11963atagen2026-10-24 23:24 UTC226
            .ok()
cbb715e11963atagen2026-10-24 23:24 UTC227
            .and_then(|poll| poll.poll.question.text.find_plain().map(str::to_owned)),
cbb715e11963atagen2026-10-24 23:24 UTC228
        "org.matrix.msc3381.poll.start" => {
cbb715e11963atagen2026-10-24 23:24 UTC229
            serde_json::from_value::<UnstablePollStartEventContent>(content.clone())
cbb715e11963atagen2026-10-24 23:24 UTC230
                .ok()
cbb715e11963atagen2026-10-24 23:24 UTC231
                .map(|poll| poll.poll_start().question.text.clone())
cbb715e11963atagen2026-10-24 23:24 UTC232
        }
cbb715e11963atagen2026-10-24 23:24 UTC233
        _ => None,
cbb715e11963atagen2026-10-24 23:24 UTC234
    };
cbb715e11963atagen2026-10-24 23:24 UTC235
    typed_question
cbb715e11963atagen2026-10-24 23:24 UTC236
        .or_else(|| {
cbb715e11963atagen2026-10-24 23:24 UTC237
            content
cbb715e11963atagen2026-10-24 23:24 UTC238
                .get("m.poll")
cbb715e11963atagen2026-10-24 23:24 UTC239
                .and_then(|poll| poll.get("question"))
cbb715e11963atagen2026-10-24 23:24 UTC240
                .and_then(|question| question.get("m.text"))
cbb715e11963atagen2026-10-24 23:24 UTC241
                .and_then(generic_text)
cbb715e11963atagen2026-10-24 23:24 UTC242
        })
cbb715e11963atagen2026-10-24 23:24 UTC243
        .or_else(|| {
cbb715e11963atagen2026-10-24 23:24 UTC244
            content
cbb715e11963atagen2026-10-24 23:24 UTC245
                .get("org.matrix.msc3381.poll.start")
cbb715e11963atagen2026-10-24 23:24 UTC246
                .and_then(|poll| poll.get("question"))
cbb715e11963atagen2026-10-24 23:24 UTC247
                .and_then(|question| question.get("org.matrix.msc1767.text"))
cbb715e11963atagen2026-10-24 23:24 UTC248
                .and_then(generic_text)
cbb715e11963atagen2026-10-24 23:24 UTC249
        })
cbb715e11963atagen2026-10-24 23:24 UTC250
        .or_else(|| {
cbb715e11963atagen2026-10-24 23:24 UTC251
            ["org.matrix.msc1767.text", "m.text", "question", "body"]
cbb715e11963atagen2026-10-24 23:24 UTC252
                .iter()
cbb715e11963atagen2026-10-24 23:24 UTC253
                .find_map(|key| content.get(*key).and_then(generic_text))
cbb715e11963atagen2026-10-24 23:24 UTC254
        })
cbb715e11963atagen2026-10-24 23:24 UTC255
}
cbb715e11963atagen2026-10-24 23:24 UTC256
cbb715e11963atagen2026-10-24 23:24 UTC257
fn generic_text(value: &Value) -> Option<String> {
cbb715e11963atagen2026-10-24 23:24 UTC258
    match value {
cbb715e11963atagen2026-10-24 23:24 UTC259
        Value::String(text) => Some(text.clone()),
cbb715e11963atagen2026-10-24 23:24 UTC260
        Value::Array(representations) => representations
cbb715e11963atagen2026-10-24 23:24 UTC261
            .iter()
cbb715e11963atagen2026-10-24 23:24 UTC262
            .find(|representation| {
cbb715e11963atagen2026-10-24 23:24 UTC263
                representation.get("mimetype").and_then(Value::as_str) == Some("text/plain")
cbb715e11963atagen2026-10-24 23:24 UTC264
            })
cbb715e11963atagen2026-10-24 23:24 UTC265
            .or_else(|| representations.first())
cbb715e11963atagen2026-10-24 23:24 UTC266
            .and_then(|representation| representation.get("body"))
cbb715e11963atagen2026-10-24 23:24 UTC267
            .and_then(Value::as_str)
cbb715e11963atagen2026-10-24 23:24 UTC268
            .map(str::to_owned),
cbb715e11963atagen2026-10-24 23:24 UTC269
        Value::Object(_) => value.get("body").and_then(Value::as_str).map(str::to_owned),
cbb715e11963atagen2026-10-24 23:24 UTC270
        _ => None,
cbb715e11963atagen2026-10-24 23:24 UTC271
    }
cbb715e11963atagen2026-10-24 23:24 UTC272
}
068bfa92dd36atagen2026-10-25 13:53 UTC273
068bfa92dd36atagen2026-10-25 13:53 UTC274
/// the typed state rows the live timeline gives these events
068bfa92dd36atagen2026-10-25 13:53 UTC275
fn state_content(value: &Value, event_type: &str) -> Option<TimelineContent> {
068bfa92dd36atagen2026-10-25 13:53 UTC276
    let content = value.get("content")?;
068bfa92dd36atagen2026-10-25 13:53 UTC277
    let text = |key: &str| {
068bfa92dd36atagen2026-10-25 13:53 UTC278
        content
068bfa92dd36atagen2026-10-25 13:53 UTC279
            .get(key)
068bfa92dd36atagen2026-10-25 13:53 UTC280
            .and_then(Value::as_str)
068bfa92dd36atagen2026-10-25 13:53 UTC281
            .filter(|text| !text.is_empty())
068bfa92dd36atagen2026-10-25 13:53 UTC282
            .map(str::to_owned)
068bfa92dd36atagen2026-10-25 13:53 UTC283
    };
068bfa92dd36atagen2026-10-25 13:53 UTC284
    Some(match event_type {
068bfa92dd36atagen2026-10-25 13:53 UTC285
        "m.room.member" => {
068bfa92dd36atagen2026-10-25 13:53 UTC286
            let event: OriginalSyncRoomMemberEvent = serde_json::from_value(value.clone()).ok()?;
068bfa92dd36atagen2026-10-25 13:53 UTC287
            let user_id = event.state_key.clone();
068bfa92dd36atagen2026-10-25 13:53 UTC288
            match event.membership_change() {
068bfa92dd36atagen2026-10-25 13:53 UTC289
                RumaMembershipChange::ProfileChanged {
068bfa92dd36atagen2026-10-25 13:53 UTC290
                    displayname_change,
068bfa92dd36atagen2026-10-25 13:53 UTC291
                    avatar_url_change,
068bfa92dd36atagen2026-10-25 13:53 UTC292
                } => TimelineContent::Profile {
068bfa92dd36atagen2026-10-25 13:53 UTC293
                    user_id,
068bfa92dd36atagen2026-10-25 13:53 UTC294
                    display_name: displayname_change.map(|change| Change {
068bfa92dd36atagen2026-10-25 13:53 UTC295
                        old: change.old.map(str::to_owned),
068bfa92dd36atagen2026-10-25 13:53 UTC296
                        new: change.new.map(str::to_owned),
068bfa92dd36atagen2026-10-25 13:53 UTC297
                    }),
068bfa92dd36atagen2026-10-25 13:53 UTC298
                    avatar: avatar_url_change.map(|change| Change {
068bfa92dd36atagen2026-10-25 13:53 UTC299
                        old: change.old.map(MediaId::plain),
068bfa92dd36atagen2026-10-25 13:53 UTC300
                        new: change.new.map(MediaId::plain),
068bfa92dd36atagen2026-10-25 13:53 UTC301
                    }),
068bfa92dd36atagen2026-10-25 13:53 UTC302
                },
068bfa92dd36atagen2026-10-25 13:53 UTC303
                change => TimelineContent::Membership {
068bfa92dd36atagen2026-10-25 13:53 UTC304
                    user_id,
068bfa92dd36atagen2026-10-25 13:53 UTC305
                    display_name: event.content.displayname.clone(),
068bfa92dd36atagen2026-10-25 13:53 UTC306
                    change: membership_change(&change),
068bfa92dd36atagen2026-10-25 13:53 UTC307
                },
068bfa92dd36atagen2026-10-25 13:53 UTC308
            }
068bfa92dd36atagen2026-10-25 13:53 UTC309
        }
068bfa92dd36atagen2026-10-25 13:53 UTC310
        "m.room.name" => TimelineContent::RoomName(text("name")),
068bfa92dd36atagen2026-10-25 13:53 UTC311
        "m.room.topic" => TimelineContent::RoomTopic(text("topic")),
068bfa92dd36atagen2026-10-25 13:53 UTC312
        "m.room.avatar" => TimelineContent::RoomAvatar(text("url").map(|url| MediaId::plain(&url))),
068bfa92dd36atagen2026-10-25 13:53 UTC313
        "m.room.encryption" => TimelineContent::EncryptionEnabled,
068bfa92dd36atagen2026-10-25 13:53 UTC314
        "m.room.create" => TimelineContent::RoomCreated,
068bfa92dd36atagen2026-10-25 13:53 UTC315
        "m.room.tombstone" => TimelineContent::RoomUpgraded {
068bfa92dd36atagen2026-10-25 13:53 UTC316
            successor: serde_json::from_value(content.get("replacement_room")?.clone()).ok()?,
068bfa92dd36atagen2026-10-25 13:53 UTC317
        },
068bfa92dd36atagen2026-10-25 13:53 UTC318
        _ => return None,
068bfa92dd36atagen2026-10-25 13:53 UTC319
    })
068bfa92dd36atagen2026-10-25 13:53 UTC320
}
068bfa92dd36atagen2026-10-25 13:53 UTC321
068bfa92dd36atagen2026-10-25 13:53 UTC322
const fn membership_change(change: &RumaMembershipChange<'_>) -> MembershipChange {
068bfa92dd36atagen2026-10-25 13:53 UTC323
    match change {
068bfa92dd36atagen2026-10-25 13:53 UTC324
        RumaMembershipChange::Joined => MembershipChange::Joined,
068bfa92dd36atagen2026-10-25 13:53 UTC325
        RumaMembershipChange::Left => MembershipChange::Left,
068bfa92dd36atagen2026-10-25 13:53 UTC326
        RumaMembershipChange::Banned => MembershipChange::Banned,
068bfa92dd36atagen2026-10-25 13:53 UTC327
        RumaMembershipChange::Unbanned => MembershipChange::Unbanned,
068bfa92dd36atagen2026-10-25 13:53 UTC328
        RumaMembershipChange::Kicked => MembershipChange::Kicked,
068bfa92dd36atagen2026-10-25 13:53 UTC329
        RumaMembershipChange::KickedAndBanned => MembershipChange::KickedAndBanned,
068bfa92dd36atagen2026-10-25 13:53 UTC330
        RumaMembershipChange::Invited => MembershipChange::Invited,
068bfa92dd36atagen2026-10-25 13:53 UTC331
        RumaMembershipChange::InvitationAccepted => MembershipChange::InvitationAccepted,
068bfa92dd36atagen2026-10-25 13:53 UTC332
        RumaMembershipChange::InvitationRejected => MembershipChange::InvitationRejected,
068bfa92dd36atagen2026-10-25 13:53 UTC333
        RumaMembershipChange::InvitationRevoked => MembershipChange::InvitationRevoked,
068bfa92dd36atagen2026-10-25 13:53 UTC334
        RumaMembershipChange::Knocked => MembershipChange::Knocked,
068bfa92dd36atagen2026-10-25 13:53 UTC335
        RumaMembershipChange::KnockAccepted => MembershipChange::KnockAccepted,
068bfa92dd36atagen2026-10-25 13:53 UTC336
        RumaMembershipChange::KnockRetracted => MembershipChange::KnockRetracted,
068bfa92dd36atagen2026-10-25 13:53 UTC337
        RumaMembershipChange::KnockDenied => MembershipChange::KnockDenied,
068bfa92dd36atagen2026-10-25 13:53 UTC338
        _ => MembershipChange::Other,
068bfa92dd36atagen2026-10-25 13:53 UTC339
    }
068bfa92dd36atagen2026-10-25 13:53 UTC340
}