atagen

macltok

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

Blame: src/engine/context.rs

cb7b3362e1ceatagen2026-10-25 09:08 UTC1
use std::{collections::HashMap, sync::Arc};
89bc9f8d246datagen2026-10-24 13:40 UTC2
cb7b3362e1ceatagen2026-10-25 09:08 UTC3
use futures_util::future::{join_all, try_join};
913832a327a8atagen2026-10-25 09:33 UTC4
use matrix_sdk::{
913832a327a8atagen2026-10-25 09:33 UTC5
    Room, deserialized_responses::TimelineEvent as SdkTimelineEvent, room::MessagesOptions,
913832a327a8atagen2026-10-25 09:33 UTC6
};
913832a327a8atagen2026-10-25 09:33 UTC7
use ruma::{OwnedEventId, OwnedUserId, UInt, push::Action};
cb7b3362e1ceatagen2026-10-25 09:08 UTC8
use serde_json::Value;
89bc9f8d246datagen2026-10-24 13:40 UTC9
89bc9f8d246datagen2026-10-24 13:40 UTC10
use crate::{
cb7b3362e1ceatagen2026-10-25 09:08 UTC11
    Error, ErrorKind, EventContext, EventContextCommand, EventContextPage, MediaId, Sender,
cb7b3362e1ceatagen2026-10-25 09:08 UTC12
    TimelineEvent, TimelineItem,
cbb715e11963atagen2026-10-24 23:24 UTC13
    engine::MediaService,
cb7b3362e1ceatagen2026-10-25 09:08 UTC14
    normalise::{
cb7b3362e1ceatagen2026-10-25 09:08 UTC15
        MediaRegistration, apply_profiles, normalise_context, normalise_observed, resolve_replies,
cb7b3362e1ceatagen2026-10-25 09:08 UTC16
    },
89bc9f8d246datagen2026-10-24 13:40 UTC17
};
89bc9f8d246datagen2026-10-24 13:40 UTC18
89bc9f8d246datagen2026-10-24 13:40 UTC19
const MAX_CONTEXT_LIMIT: u16 = 50;
89bc9f8d246datagen2026-10-24 13:40 UTC20
89bc9f8d246datagen2026-10-24 13:40 UTC21
pub(crate) async fn execute(
89bc9f8d246datagen2026-10-24 13:40 UTC22
    room: Room,
cbb715e11963atagen2026-10-24 23:24 UTC23
    current_user_id: Option<OwnedUserId>,
cbb715e11963atagen2026-10-24 23:24 UTC24
    media: Arc<MediaService>,
89bc9f8d246datagen2026-10-24 13:40 UTC25
    command: EventContextCommand,
89bc9f8d246datagen2026-10-24 13:40 UTC26
) -> Result<crate::CommandOutput, Error> {
cb7b3362e1ceatagen2026-10-25 09:08 UTC27
    let (token, limit, backwards) = match command {
89bc9f8d246datagen2026-10-24 13:40 UTC28
        EventContextCommand::Fetch {
89bc9f8d246datagen2026-10-24 13:40 UTC29
            event_id,
89bc9f8d246datagen2026-10-24 13:40 UTC30
            before_limit,
89bc9f8d246datagen2026-10-24 13:40 UTC31
            after_limit,
89bc9f8d246datagen2026-10-24 13:40 UTC32
            ..
89bc9f8d246datagen2026-10-24 13:40 UTC33
        } => {
cb7b3362e1ceatagen2026-10-25 09:08 UTC34
            let (context, registrations, _) =
cb7b3362e1ceatagen2026-10-25 09:08 UTC35
                capture(room, current_user_id, event_id, before_limit, after_limit).await?;
cb7b3362e1ceatagen2026-10-25 09:08 UTC36
            media.register(registrations).await;
cb7b3362e1ceatagen2026-10-25 09:08 UTC37
            return Ok(crate::CommandOutput::EventContext(context));
89bc9f8d246datagen2026-10-24 13:40 UTC38
        }
cb7b3362e1ceatagen2026-10-25 09:08 UTC39
        EventContextCommand::Before { token, limit, .. } => (token, limit, true),
cb7b3362e1ceatagen2026-10-25 09:08 UTC40
        EventContextCommand::After { token, limit, .. } => (token, limit, false),
89bc9f8d246datagen2026-10-24 13:40 UTC41
    };
cb7b3362e1ceatagen2026-10-25 09:08 UTC42
    if limit == 0 {
cb7b3362e1ceatagen2026-10-25 09:08 UTC43
        return Err(Error::new(
cb7b3362e1ceatagen2026-10-25 09:08 UTC44
            ErrorKind::Invalid,
cb7b3362e1ceatagen2026-10-25 09:08 UTC45
            "context page limit must be at least 1",
cb7b3362e1ceatagen2026-10-25 09:08 UTC46
        ));
cb7b3362e1ceatagen2026-10-25 09:08 UTC47
    }
cb7b3362e1ceatagen2026-10-25 09:08 UTC48
    validate_limit(limit)?;
cb7b3362e1ceatagen2026-10-25 09:08 UTC49
    if token.is_empty() {
cb7b3362e1ceatagen2026-10-25 09:08 UTC50
        return Err(Error::new(
cb7b3362e1ceatagen2026-10-25 09:08 UTC51
            ErrorKind::Invalid,
cb7b3362e1ceatagen2026-10-25 09:08 UTC52
            "context pagination token is empty",
cb7b3362e1ceatagen2026-10-25 09:08 UTC53
        ));
cb7b3362e1ceatagen2026-10-25 09:08 UTC54
    }
cb7b3362e1ceatagen2026-10-25 09:08 UTC55
    let side = fetch_side(&room, Some(&token), limit, backwards).await?;
913832a327a8atagen2026-10-25 09:33 UTC56
    let mut page = Assembly::new(
913832a327a8atagen2026-10-25 09:33 UTC57
        &room,
913832a327a8atagen2026-10-25 09:33 UTC58
        current_user_id.as_deref(),
913832a327a8atagen2026-10-25 09:33 UTC59
        &side.values,
913832a327a8atagen2026-10-25 09:33 UTC60
        &side.highlights,
913832a327a8atagen2026-10-25 09:33 UTC61
        None,
913832a327a8atagen2026-10-25 09:33 UTC62
    )
913832a327a8atagen2026-10-25 09:33 UTC63
    .await;
cb7b3362e1ceatagen2026-10-25 09:08 UTC64
    let events = page.take(0..side.values.len());
cb7b3362e1ceatagen2026-10-25 09:08 UTC65
    media.register(page.media).await;
cb7b3362e1ceatagen2026-10-25 09:08 UTC66
    Ok(crate::CommandOutput::EventContextPage(EventContextPage {
cb7b3362e1ceatagen2026-10-25 09:08 UTC67
        events,
cb7b3362e1ceatagen2026-10-25 09:08 UTC68
        next_token: side.token,
cb7b3362e1ceatagen2026-10-25 09:08 UTC69
    }))
cbb715e11963atagen2026-10-24 23:24 UTC70
}
89bc9f8d246datagen2026-10-24 13:40 UTC71
cbb715e11963atagen2026-10-24 23:24 UTC72
pub(crate) async fn capture(
cbb715e11963atagen2026-10-24 23:24 UTC73
    room: Room,
cbb715e11963atagen2026-10-24 23:24 UTC74
    current_user_id: Option<OwnedUserId>,
cbb715e11963atagen2026-10-24 23:24 UTC75
    event_id: OwnedEventId,
cbb715e11963atagen2026-10-24 23:24 UTC76
    before_limit: u16,
cbb715e11963atagen2026-10-24 23:24 UTC77
    after_limit: u16,
cb7b3362e1ceatagen2026-10-25 09:08 UTC78
) -> Result<(EventContext, Vec<MediaRegistration>, Vec<Value>), Error> {
cbb715e11963atagen2026-10-24 23:24 UTC79
    validate_limit(before_limit)?;
cbb715e11963atagen2026-10-24 23:24 UTC80
    validate_limit(after_limit)?;
cbb715e11963atagen2026-10-24 23:24 UTC81
    let raw = room
cbb715e11963atagen2026-10-24 23:24 UTC82
        .event_with_context(&event_id, false, UInt::from(0_u16), None)
89bc9f8d246datagen2026-10-24 13:40 UTC83
        .await
89bc9f8d246datagen2026-10-24 13:40 UTC84
        .map_err(|source| {
cbb715e11963atagen2026-10-24 23:24 UTC85
            let error = Error::from_sdk("fetching event context failed", source);
cbb715e11963atagen2026-10-24 23:24 UTC86
            if error
cbb715e11963atagen2026-10-24 23:24 UTC87
                .server
cbb715e11963atagen2026-10-24 23:24 UTC88
                .as_ref()
cbb715e11963atagen2026-10-24 23:24 UTC89
                .is_some_and(|server| server.status == 404)
cbb715e11963atagen2026-10-24 23:24 UTC90
            {
cbb715e11963atagen2026-10-24 23:24 UTC91
                Error::new(ErrorKind::EventNotFound, "event was not found")
cbb715e11963atagen2026-10-24 23:24 UTC92
            } else {
cbb715e11963atagen2026-10-24 23:24 UTC93
                error
cbb715e11963atagen2026-10-24 23:24 UTC94
            }
89bc9f8d246datagen2026-10-24 13:40 UTC95
        })?;
913832a327a8atagen2026-10-25 09:33 UTC96
    let (target, target_highlight) = raw
cbb715e11963atagen2026-10-24 23:24 UTC97
        .event
cbb715e11963atagen2026-10-24 23:24 UTC98
        .as_ref()
913832a327a8atagen2026-10-25 09:33 UTC99
        .and_then(raw_event)
cb7b3362e1ceatagen2026-10-25 09:08 UTC100
        .ok_or_else(|| Error::new(ErrorKind::EventNotFound, "event was not found"))?;
cb7b3362e1ceatagen2026-10-25 09:08 UTC101
    let (before, after) = try_join(
cb7b3362e1ceatagen2026-10-25 09:08 UTC102
        fetch_side(&room, raw.prev_batch_token.as_deref(), before_limit, true),
cb7b3362e1ceatagen2026-10-25 09:08 UTC103
        fetch_side(&room, raw.next_batch_token.as_deref(), after_limit, false),
cb7b3362e1ceatagen2026-10-25 09:08 UTC104
    )
cb7b3362e1ceatagen2026-10-25 09:08 UTC105
    .await?;
cb7b3362e1ceatagen2026-10-25 09:08 UTC106
    let target_index = before.values.len();
cb7b3362e1ceatagen2026-10-25 09:08 UTC107
    let mut values = before.values;
cb7b3362e1ceatagen2026-10-25 09:08 UTC108
    values.push(target);
cb7b3362e1ceatagen2026-10-25 09:08 UTC109
    values.extend(after.values);
913832a327a8atagen2026-10-25 09:33 UTC110
    let mut highlights = before.highlights;
913832a327a8atagen2026-10-25 09:33 UTC111
    highlights.push(target_highlight);
913832a327a8atagen2026-10-25 09:33 UTC112
    highlights.extend(after.highlights);
cb7b3362e1ceatagen2026-10-25 09:08 UTC113
    let mut page = Assembly::new(
cb7b3362e1ceatagen2026-10-25 09:08 UTC114
        &room,
cb7b3362e1ceatagen2026-10-25 09:08 UTC115
        current_user_id.as_deref(),
cb7b3362e1ceatagen2026-10-25 09:08 UTC116
        &values,
913832a327a8atagen2026-10-25 09:33 UTC117
        &highlights,
cb7b3362e1ceatagen2026-10-25 09:08 UTC118
        Some(target_index),
cb7b3362e1ceatagen2026-10-25 09:08 UTC119
    )
cb7b3362e1ceatagen2026-10-25 09:08 UTC120
    .await;
cb7b3362e1ceatagen2026-10-25 09:08 UTC121
    let events_before = page.take(0..target_index);
cb7b3362e1ceatagen2026-10-25 09:08 UTC122
    let event = page
cb7b3362e1ceatagen2026-10-25 09:08 UTC123
        .take(target_index..target_index + 1)
cb7b3362e1ceatagen2026-10-25 09:08 UTC124
        .pop()
cb7b3362e1ceatagen2026-10-25 09:08 UTC125
        .ok_or_else(|| Error::new(ErrorKind::EventNotFound, "event could not be read"))?;
cb7b3362e1ceatagen2026-10-25 09:08 UTC126
    let events_after = page.take(target_index + 1..values.len());
cbb715e11963atagen2026-10-24 23:24 UTC127
    Ok((
cbb715e11963atagen2026-10-24 23:24 UTC128
        EventContext {
cbb715e11963atagen2026-10-24 23:24 UTC129
            events_before,
cbb715e11963atagen2026-10-24 23:24 UTC130
            event,
cbb715e11963atagen2026-10-24 23:24 UTC131
            events_after,
cb7b3362e1ceatagen2026-10-25 09:08 UTC132
            start_token: before.token,
cb7b3362e1ceatagen2026-10-25 09:08 UTC133
            end_token: after.token,
cbb715e11963atagen2026-10-24 23:24 UTC134
        },
cb7b3362e1ceatagen2026-10-25 09:08 UTC135
        page.media,
cb7b3362e1ceatagen2026-10-25 09:08 UTC136
        values,
cbb715e11963atagen2026-10-24 23:24 UTC137
    ))
cbb715e11963atagen2026-10-24 23:24 UTC138
}
89bc9f8d246datagen2026-10-24 13:40 UTC139
cb7b3362e1ceatagen2026-10-25 09:08 UTC140
struct Side {
cb7b3362e1ceatagen2026-10-25 09:08 UTC141
    /// in timeline order
cb7b3362e1ceatagen2026-10-25 09:08 UTC142
    values: Vec<Value>,
913832a327a8atagen2026-10-25 09:33 UTC143
    /// beside `values`, from the push actions the sdk worked out as it fetched
913832a327a8atagen2026-10-25 09:33 UTC144
    highlights: Vec<bool>,
cb7b3362e1ceatagen2026-10-25 09:08 UTC145
    token: Option<String>,
cb7b3362e1ceatagen2026-10-25 09:08 UTC146
}
cb7b3362e1ceatagen2026-10-25 09:08 UTC147
913832a327a8atagen2026-10-25 09:33 UTC148
fn raw_event(event: &SdkTimelineEvent) -> Option<(Value, bool)> {
913832a327a8atagen2026-10-25 09:33 UTC149
    let value = serde_json::from_str(event.raw().json().get()).ok()?;
913832a327a8atagen2026-10-25 09:33 UTC150
    let highlight = event
913832a327a8atagen2026-10-25 09:33 UTC151
        .push_actions()
913832a327a8atagen2026-10-25 09:33 UTC152
        .is_some_and(|actions| actions.iter().any(Action::is_highlight));
913832a327a8atagen2026-10-25 09:33 UTC153
    Some((value, highlight))
913832a327a8atagen2026-10-25 09:33 UTC154
}
913832a327a8atagen2026-10-25 09:33 UTC155
cbb715e11963atagen2026-10-24 23:24 UTC156
async fn fetch_side(
cbb715e11963atagen2026-10-24 23:24 UTC157
    room: &Room,
cbb715e11963atagen2026-10-24 23:24 UTC158
    token: Option<&str>,
cbb715e11963atagen2026-10-24 23:24 UTC159
    limit: u16,
cbb715e11963atagen2026-10-24 23:24 UTC160
    backwards: bool,
cb7b3362e1ceatagen2026-10-25 09:08 UTC161
) -> Result<Side, Error> {
cbb715e11963atagen2026-10-24 23:24 UTC162
    let Some(token) = token else {
cb7b3362e1ceatagen2026-10-25 09:08 UTC163
        return Ok(Side {
cb7b3362e1ceatagen2026-10-25 09:08 UTC164
            values: Vec::new(),
913832a327a8atagen2026-10-25 09:33 UTC165
            highlights: Vec::new(),
cb7b3362e1ceatagen2026-10-25 09:08 UTC166
            token: None,
cb7b3362e1ceatagen2026-10-25 09:08 UTC167
        });
cbb715e11963atagen2026-10-24 23:24 UTC168
    };
cb7b3362e1ceatagen2026-10-25 09:08 UTC169
    if limit == 0 {
cb7b3362e1ceatagen2026-10-25 09:08 UTC170
        return Ok(Side {
cb7b3362e1ceatagen2026-10-25 09:08 UTC171
            values: Vec::new(),
913832a327a8atagen2026-10-25 09:33 UTC172
            highlights: Vec::new(),
cb7b3362e1ceatagen2026-10-25 09:08 UTC173
            token: Some(token.to_owned()),
cb7b3362e1ceatagen2026-10-25 09:08 UTC174
        });
cb7b3362e1ceatagen2026-10-25 09:08 UTC175
    }
cbb715e11963atagen2026-10-24 23:24 UTC176
    let mut options = if backwards {
cbb715e11963atagen2026-10-24 23:24 UTC177
        MessagesOptions::backward()
cbb715e11963atagen2026-10-24 23:24 UTC178
    } else {
cbb715e11963atagen2026-10-24 23:24 UTC179
        MessagesOptions::forward()
cbb715e11963atagen2026-10-24 23:24 UTC180
    };
cbb715e11963atagen2026-10-24 23:24 UTC181
    options = options.from(token);
cbb715e11963atagen2026-10-24 23:24 UTC182
    options.limit = UInt::from(limit);
cbb715e11963atagen2026-10-24 23:24 UTC183
    let messages = room
cbb715e11963atagen2026-10-24 23:24 UTC184
        .messages(options)
cbb715e11963atagen2026-10-24 23:24 UTC185
        .await
cbb715e11963atagen2026-10-24 23:24 UTC186
        .map_err(|source| Error::from_sdk("fetching event context events failed", source))?;
913832a327a8atagen2026-10-25 09:33 UTC187
    let mut events: Vec<_> = messages.chunk.iter().filter_map(raw_event).collect();
89bc9f8d246datagen2026-10-24 13:40 UTC188
    if backwards {
913832a327a8atagen2026-10-25 09:33 UTC189
        events.reverse();
89bc9f8d246datagen2026-10-24 13:40 UTC190
    }
913832a327a8atagen2026-10-25 09:33 UTC191
    let (values, highlights) = events.into_iter().unzip();
cb7b3362e1ceatagen2026-10-25 09:08 UTC192
    Ok(Side {
cb7b3362e1ceatagen2026-10-25 09:08 UTC193
        values,
913832a327a8atagen2026-10-25 09:33 UTC194
        highlights,
cb7b3362e1ceatagen2026-10-25 09:08 UTC195
        token: messages.end,
cb7b3362e1ceatagen2026-10-25 09:08 UTC196
    })
89bc9f8d246datagen2026-10-24 13:40 UTC197
}
89bc9f8d246datagen2026-10-24 13:40 UTC198
cb7b3362e1ceatagen2026-10-25 09:08 UTC199
/// a window of raw events normalised together, so relations inside it land on their targets
cb7b3362e1ceatagen2026-10-25 09:08 UTC200
struct Assembly {
cb7b3362e1ceatagen2026-10-25 09:08 UTC201
    items: Vec<(usize, TimelineItem)>,
cb7b3362e1ceatagen2026-10-25 09:08 UTC202
    media: Vec<MediaRegistration>,
cb7b3362e1ceatagen2026-10-25 09:08 UTC203
}
cb7b3362e1ceatagen2026-10-25 09:08 UTC204
cb7b3362e1ceatagen2026-10-25 09:08 UTC205
impl Assembly {
cb7b3362e1ceatagen2026-10-25 09:08 UTC206
    async fn new(
cb7b3362e1ceatagen2026-10-25 09:08 UTC207
        room: &Room,
cb7b3362e1ceatagen2026-10-25 09:08 UTC208
        current_user_id: Option<&ruma::UserId>,
cb7b3362e1ceatagen2026-10-25 09:08 UTC209
        values: &[Value],
913832a327a8atagen2026-10-25 09:33 UTC210
        highlights: &[bool],
cb7b3362e1ceatagen2026-10-25 09:08 UTC211
        keep: Option<usize>,
cb7b3362e1ceatagen2026-10-25 09:08 UTC212
    ) -> Self {
cb7b3362e1ceatagen2026-10-25 09:08 UTC213
        let page = normalise_context(values, keep, current_user_id, room.room_id());
cb7b3362e1ceatagen2026-10-25 09:08 UTC214
        let mut items = page.items;
913832a327a8atagen2026-10-25 09:33 UTC215
        for (index, item) in &mut items {
913832a327a8atagen2026-10-25 09:33 UTC216
            if let TimelineItem::Event(event) = item {
913832a327a8atagen2026-10-25 09:33 UTC217
                event.highlight = highlights[*index];
913832a327a8atagen2026-10-25 09:33 UTC218
            }
913832a327a8atagen2026-10-25 09:33 UTC219
        }
cb7b3362e1ceatagen2026-10-25 09:08 UTC220
        let mut media = page.media;
cb7b3362e1ceatagen2026-10-25 09:08 UTC221
        let mut targets: HashMap<OwnedEventId, TimelineEvent> = HashMap::new();
cb7b3362e1ceatagen2026-10-25 09:08 UTC222
        let mut missing = Vec::new();
cb7b3362e1ceatagen2026-10-25 09:08 UTC223
        for (_, item) in &items {
cb7b3362e1ceatagen2026-10-25 09:08 UTC224
            if let TimelineItem::Event(event) = item
cb7b3362e1ceatagen2026-10-25 09:08 UTC225
                && let Some(event_id) = &event.event_id
cb7b3362e1ceatagen2026-10-25 09:08 UTC226
            {
cb7b3362e1ceatagen2026-10-25 09:08 UTC227
                targets.insert(event_id.clone(), (**event).clone());
cb7b3362e1ceatagen2026-10-25 09:08 UTC228
            }
cb7b3362e1ceatagen2026-10-25 09:08 UTC229
        }
cb7b3362e1ceatagen2026-10-25 09:08 UTC230
        for event in events(&mut items) {
cb7b3362e1ceatagen2026-10-25 09:08 UTC231
            if let Some(reply) = &event.reply_to
cb7b3362e1ceatagen2026-10-25 09:08 UTC232
                && !targets.contains_key(&reply.event_id)
cb7b3362e1ceatagen2026-10-25 09:08 UTC233
                && !missing.contains(&reply.event_id)
cb7b3362e1ceatagen2026-10-25 09:08 UTC234
            {
cb7b3362e1ceatagen2026-10-25 09:08 UTC235
                missing.push(reply.event_id.clone());
cb7b3362e1ceatagen2026-10-25 09:08 UTC236
            }
cb7b3362e1ceatagen2026-10-25 09:08 UTC237
        }
cb7b3362e1ceatagen2026-10-25 09:08 UTC238
        let fetched = join_all(
cb7b3362e1ceatagen2026-10-25 09:08 UTC239
            missing
cb7b3362e1ceatagen2026-10-25 09:08 UTC240
                .into_iter()
cb7b3362e1ceatagen2026-10-25 09:08 UTC241
                .map(|event_id| fetch_reply(room, current_user_id, event_id)),
cb7b3362e1ceatagen2026-10-25 09:08 UTC242
        )
cb7b3362e1ceatagen2026-10-25 09:08 UTC243
        .await;
cb7b3362e1ceatagen2026-10-25 09:08 UTC244
        for (event, registrations) in fetched.into_iter().flatten() {
cbb715e11963atagen2026-10-24 23:24 UTC245
            media.extend(registrations);
cb7b3362e1ceatagen2026-10-25 09:08 UTC246
            if let Some(event_id) = event.event_id.clone() {
cb7b3362e1ceatagen2026-10-25 09:08 UTC247
                targets.insert(event_id, event);
cb7b3362e1ceatagen2026-10-25 09:08 UTC248
            }
cbb715e11963atagen2026-10-24 23:24 UTC249
        }
cb7b3362e1ceatagen2026-10-25 09:08 UTC250
        let profiles = profiles(
cb7b3362e1ceatagen2026-10-25 09:08 UTC251
            room,
cb7b3362e1ceatagen2026-10-25 09:08 UTC252
            items
cb7b3362e1ceatagen2026-10-25 09:08 UTC253
                .iter()
cb7b3362e1ceatagen2026-10-25 09:08 UTC254
                .filter_map(|(_, item)| match item {
cb7b3362e1ceatagen2026-10-25 09:08 UTC255
                    TimelineItem::Event(event) => Some(&event.sender.user_id),
cb7b3362e1ceatagen2026-10-25 09:08 UTC256
                    _ => None,
cb7b3362e1ceatagen2026-10-25 09:08 UTC257
                })
cb7b3362e1ceatagen2026-10-25 09:08 UTC258
                .chain(targets.values().map(|event| &event.sender.user_id)),
cb7b3362e1ceatagen2026-10-25 09:08 UTC259
        )
cb7b3362e1ceatagen2026-10-25 09:08 UTC260
        .await;
cb7b3362e1ceatagen2026-10-25 09:08 UTC261
        apply_profiles(targets.values_mut(), &profiles);
cb7b3362e1ceatagen2026-10-25 09:08 UTC262
        apply_profiles(events(&mut items), &profiles);
cb7b3362e1ceatagen2026-10-25 09:08 UTC263
        resolve_replies(events(&mut items), &targets);
cb7b3362e1ceatagen2026-10-25 09:08 UTC264
        Self { items, media }
89bc9f8d246datagen2026-10-24 13:40 UTC265
    }
cb7b3362e1ceatagen2026-10-25 09:08 UTC266
cb7b3362e1ceatagen2026-10-25 09:08 UTC267
    /// the items that came from raw events in `range`
cb7b3362e1ceatagen2026-10-25 09:08 UTC268
    fn take(&mut self, range: std::ops::Range<usize>) -> Vec<TimelineItem> {
cb7b3362e1ceatagen2026-10-25 09:08 UTC269
        let (taken, kept) = std::mem::take(&mut self.items)
cb7b3362e1ceatagen2026-10-25 09:08 UTC270
            .into_iter()
cb7b3362e1ceatagen2026-10-25 09:08 UTC271
            .partition(|(index, _)| range.contains(index));
cb7b3362e1ceatagen2026-10-25 09:08 UTC272
        self.items = kept;
cb7b3362e1ceatagen2026-10-25 09:08 UTC273
        taken.into_iter().map(|(_, item)| item).collect()
cb7b3362e1ceatagen2026-10-25 09:08 UTC274
    }
cb7b3362e1ceatagen2026-10-25 09:08 UTC275
}
cb7b3362e1ceatagen2026-10-25 09:08 UTC276
cb7b3362e1ceatagen2026-10-25 09:08 UTC277
fn events(items: &mut [(usize, TimelineItem)]) -> impl Iterator<Item = &mut TimelineEvent> {
cb7b3362e1ceatagen2026-10-25 09:08 UTC278
    items.iter_mut().filter_map(|(_, item)| match item {
cb7b3362e1ceatagen2026-10-25 09:08 UTC279
        TimelineItem::Event(event) => Some(&mut **event),
cb7b3362e1ceatagen2026-10-25 09:08 UTC280
        _ => None,
cb7b3362e1ceatagen2026-10-25 09:08 UTC281
    })
89bc9f8d246datagen2026-10-24 13:40 UTC282
}
89bc9f8d246datagen2026-10-24 13:40 UTC283
cb7b3362e1ceatagen2026-10-25 09:08 UTC284
async fn fetch_reply(
cb7b3362e1ceatagen2026-10-25 09:08 UTC285
    room: &Room,
89bc9f8d246datagen2026-10-24 13:40 UTC286
    current_user_id: Option<&ruma::UserId>,
cb7b3362e1ceatagen2026-10-25 09:08 UTC287
    event_id: OwnedEventId,
cb7b3362e1ceatagen2026-10-25 09:08 UTC288
) -> Option<(TimelineEvent, Vec<MediaRegistration>)> {
cb7b3362e1ceatagen2026-10-25 09:08 UTC289
    let event = room
cb7b3362e1ceatagen2026-10-25 09:08 UTC290
        .load_or_fetch_event(&event_id, None)
cb7b3362e1ceatagen2026-10-25 09:08 UTC291
        .await
cb7b3362e1ceatagen2026-10-25 09:08 UTC292
        .inspect_err(|error| tracing::debug!(%event_id, %error, "fetching a reply target failed"))
cb7b3362e1ceatagen2026-10-25 09:08 UTC293
        .ok()?;
cb7b3362e1ceatagen2026-10-25 09:08 UTC294
    let value: Value = serde_json::from_str(event.raw().json().get()).ok()?;
cb7b3362e1ceatagen2026-10-25 09:08 UTC295
    let identity = format!("{}\n{event_id}", room.room_id());
cb7b3362e1ceatagen2026-10-25 09:08 UTC296
    match normalise_observed(&value, current_user_id, &identity)? {
cb7b3362e1ceatagen2026-10-25 09:08 UTC297
        (TimelineItem::Event(event), media) => Some((*event, media)),
cb7b3362e1ceatagen2026-10-25 09:08 UTC298
        _ => None,
cb7b3362e1ceatagen2026-10-25 09:08 UTC299
    }
cb7b3362e1ceatagen2026-10-25 09:08 UTC300
}
cb7b3362e1ceatagen2026-10-25 09:08 UTC301
cb7b3362e1ceatagen2026-10-25 09:08 UTC302
/// under lazy-loaded members a sender may be unknown locally, and `get_member` fetches the
cb7b3362e1ceatagen2026-10-25 09:08 UTC303
/// member list once for the room when it is
cb7b3362e1ceatagen2026-10-25 09:08 UTC304
async fn profiles<'a>(
cb7b3362e1ceatagen2026-10-25 09:08 UTC305
    room: &Room,
cb7b3362e1ceatagen2026-10-25 09:08 UTC306
    user_ids: impl Iterator<Item = &'a OwnedUserId>,
cb7b3362e1ceatagen2026-10-25 09:08 UTC307
) -> HashMap<OwnedUserId, Sender> {
cb7b3362e1ceatagen2026-10-25 09:08 UTC308
    let mut unique: Vec<&OwnedUserId> = user_ids.collect();
cb7b3362e1ceatagen2026-10-25 09:08 UTC309
    unique.sort();
cb7b3362e1ceatagen2026-10-25 09:08 UTC310
    unique.dedup();
cb7b3362e1ceatagen2026-10-25 09:08 UTC311
    join_all(unique.into_iter().map(|user_id| async move {
cb7b3362e1ceatagen2026-10-25 09:08 UTC312
        let member = room.get_member(user_id).await.ok().flatten()?;
cb7b3362e1ceatagen2026-10-25 09:08 UTC313
        Some((
cb7b3362e1ceatagen2026-10-25 09:08 UTC314
            user_id.clone(),
cb7b3362e1ceatagen2026-10-25 09:08 UTC315
            Sender {
cb7b3362e1ceatagen2026-10-25 09:08 UTC316
                user_id: user_id.clone(),
cb7b3362e1ceatagen2026-10-25 09:08 UTC317
                display_name: member.display_name().map(str::to_owned),
cb7b3362e1ceatagen2026-10-25 09:08 UTC318
                avatar: member.avatar_url().map(MediaId::plain),
cb7b3362e1ceatagen2026-10-25 09:08 UTC319
                name_ambiguous: member.name_ambiguous(),
cb7b3362e1ceatagen2026-10-25 09:08 UTC320
            },
cb7b3362e1ceatagen2026-10-25 09:08 UTC321
        ))
cb7b3362e1ceatagen2026-10-25 09:08 UTC322
    }))
cb7b3362e1ceatagen2026-10-25 09:08 UTC323
    .await
cb7b3362e1ceatagen2026-10-25 09:08 UTC324
    .into_iter()
cb7b3362e1ceatagen2026-10-25 09:08 UTC325
    .flatten()
cb7b3362e1ceatagen2026-10-25 09:08 UTC326
    .collect()
89bc9f8d246datagen2026-10-24 13:40 UTC327
}
89bc9f8d246datagen2026-10-24 13:40 UTC328
cbb715e11963atagen2026-10-24 23:24 UTC329
fn validate_limit(limit: u16) -> Result<(), Error> {
cbb715e11963atagen2026-10-24 23:24 UTC330
    if limit > MAX_CONTEXT_LIMIT {
cbb715e11963atagen2026-10-24 23:24 UTC331
        Err(Error::new(ErrorKind::Invalid, "context limit exceeds 50"))
cbb715e11963atagen2026-10-24 23:24 UTC332
    } else {
cbb715e11963atagen2026-10-24 23:24 UTC333
        Ok(())
cbb715e11963atagen2026-10-24 23:24 UTC334
    }
89bc9f8d246datagen2026-10-24 13:40 UTC335
}