atagen

macltok

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