atagen

macltok

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

Blame: src/engine/context.rs

89bc9f8d246datagen2026-10-24 13:40 UTC1
use std::sync::Arc;
89bc9f8d246datagen2026-10-24 13:40 UTC2
89bc9f8d246datagen2026-10-24 13:40 UTC3
use matrix_sdk::{room::MessagesOptions, Room};
89bc9f8d246datagen2026-10-24 13:40 UTC4
use matrix_sdk_ui::timeline::{RoomExt, TimelineEventFocusThreadMode, TimelineFocus};
89bc9f8d246datagen2026-10-24 13:40 UTC5
use ruma::{uint, OwnedEventId};
89bc9f8d246datagen2026-10-24 13:40 UTC6
89bc9f8d246datagen2026-10-24 13:40 UTC7
use crate::{
89bc9f8d246datagen2026-10-24 13:40 UTC8
    normalise::{normalise_timeline, MediaScope},
89bc9f8d246datagen2026-10-24 13:40 UTC9
    Error, ErrorKind, EventContext, EventContextCommand, EventContextDirection, EventContextPage,
89bc9f8d246datagen2026-10-24 13:40 UTC10
    TimelineItem,
89bc9f8d246datagen2026-10-24 13:40 UTC11
};
89bc9f8d246datagen2026-10-24 13:40 UTC12
89bc9f8d246datagen2026-10-24 13:40 UTC13
const MAX_CONTEXT_LIMIT: u16 = 50;
89bc9f8d246datagen2026-10-24 13:40 UTC14
89bc9f8d246datagen2026-10-24 13:40 UTC15
pub(crate) async fn execute(
89bc9f8d246datagen2026-10-24 13:40 UTC16
    room: Room,
89bc9f8d246datagen2026-10-24 13:40 UTC17
    current_user_id: Option<ruma::OwnedUserId>,
89bc9f8d246datagen2026-10-24 13:40 UTC18
    command: EventContextCommand,
89bc9f8d246datagen2026-10-24 13:40 UTC19
) -> Result<crate::CommandOutput, Error> {
89bc9f8d246datagen2026-10-24 13:40 UTC20
    if let EventContextCommand::Before { token, limit, .. }
89bc9f8d246datagen2026-10-24 13:40 UTC21
    | EventContextCommand::After { token, limit, .. } = &command
89bc9f8d246datagen2026-10-24 13:40 UTC22
    {
89bc9f8d246datagen2026-10-24 13:40 UTC23
        validate_limit(*limit)?;
89bc9f8d246datagen2026-10-24 13:40 UTC24
        if token.is_empty() {
89bc9f8d246datagen2026-10-24 13:40 UTC25
            return Err(Error::new(
89bc9f8d246datagen2026-10-24 13:40 UTC26
                ErrorKind::Invalid,
89bc9f8d246datagen2026-10-24 13:40 UTC27
                "context pagination token is empty",
89bc9f8d246datagen2026-10-24 13:40 UTC28
            ));
89bc9f8d246datagen2026-10-24 13:40 UTC29
        }
89bc9f8d246datagen2026-10-24 13:40 UTC30
        return execute_page(
89bc9f8d246datagen2026-10-24 13:40 UTC31
            room,
89bc9f8d246datagen2026-10-24 13:40 UTC32
            current_user_id,
89bc9f8d246datagen2026-10-24 13:40 UTC33
            token,
89bc9f8d246datagen2026-10-24 13:40 UTC34
            *limit,
89bc9f8d246datagen2026-10-24 13:40 UTC35
            matches!(command, EventContextCommand::Before { .. }),
89bc9f8d246datagen2026-10-24 13:40 UTC36
        )
89bc9f8d246datagen2026-10-24 13:40 UTC37
        .await;
89bc9f8d246datagen2026-10-24 13:40 UTC38
    }
89bc9f8d246datagen2026-10-24 13:40 UTC39
89bc9f8d246datagen2026-10-24 13:40 UTC40
    let (event_id, limit) = match &command {
89bc9f8d246datagen2026-10-24 13:40 UTC41
        EventContextCommand::Fetch {
89bc9f8d246datagen2026-10-24 13:40 UTC42
            event_id,
89bc9f8d246datagen2026-10-24 13:40 UTC43
            before_limit,
89bc9f8d246datagen2026-10-24 13:40 UTC44
            after_limit,
89bc9f8d246datagen2026-10-24 13:40 UTC45
            ..
89bc9f8d246datagen2026-10-24 13:40 UTC46
        } => {
89bc9f8d246datagen2026-10-24 13:40 UTC47
            validate_limit(*before_limit)?;
89bc9f8d246datagen2026-10-24 13:40 UTC48
            validate_limit(*after_limit)?;
89bc9f8d246datagen2026-10-24 13:40 UTC49
            (event_id.clone(), (*before_limit).max(*after_limit))
89bc9f8d246datagen2026-10-24 13:40 UTC50
        }
89bc9f8d246datagen2026-10-24 13:40 UTC51
        EventContextCommand::Before { .. } | EventContextCommand::After { .. } => unreachable!(),
89bc9f8d246datagen2026-10-24 13:40 UTC52
    };
89bc9f8d246datagen2026-10-24 13:40 UTC53
    validate_limit(limit)?;
89bc9f8d246datagen2026-10-24 13:40 UTC54
89bc9f8d246datagen2026-10-24 13:40 UTC55
    let raw_context = if matches!(command, EventContextCommand::Fetch { .. }) {
89bc9f8d246datagen2026-10-24 13:40 UTC56
        Some(
89bc9f8d246datagen2026-10-24 13:40 UTC57
            room.event_with_context(&event_id, false, uint!(0), None)
89bc9f8d246datagen2026-10-24 13:40 UTC58
                .await
89bc9f8d246datagen2026-10-24 13:40 UTC59
                .map_err(|source| {
89bc9f8d246datagen2026-10-24 13:40 UTC60
                    let error = Error::from_sdk("fetching event context failed", source);
89bc9f8d246datagen2026-10-24 13:40 UTC61
                    if error
89bc9f8d246datagen2026-10-24 13:40 UTC62
                        .server
89bc9f8d246datagen2026-10-24 13:40 UTC63
                        .as_ref()
89bc9f8d246datagen2026-10-24 13:40 UTC64
                        .is_some_and(|server| server.status == 404)
89bc9f8d246datagen2026-10-24 13:40 UTC65
                    {
89bc9f8d246datagen2026-10-24 13:40 UTC66
                        Error::new(ErrorKind::EventNotFound, "event was not found")
89bc9f8d246datagen2026-10-24 13:40 UTC67
                    } else {
89bc9f8d246datagen2026-10-24 13:40 UTC68
                        error
89bc9f8d246datagen2026-10-24 13:40 UTC69
                    }
89bc9f8d246datagen2026-10-24 13:40 UTC70
                })?,
89bc9f8d246datagen2026-10-24 13:40 UTC71
        )
89bc9f8d246datagen2026-10-24 13:40 UTC72
    } else {
89bc9f8d246datagen2026-10-24 13:40 UTC73
        None
89bc9f8d246datagen2026-10-24 13:40 UTC74
    };
89bc9f8d246datagen2026-10-24 13:40 UTC75
89bc9f8d246datagen2026-10-24 13:40 UTC76
    let focus = TimelineFocus::Event {
89bc9f8d246datagen2026-10-24 13:40 UTC77
        target: event_id.clone(),
89bc9f8d246datagen2026-10-24 13:40 UTC78
        num_context_events: limit,
89bc9f8d246datagen2026-10-24 13:40 UTC79
        thread_mode: TimelineEventFocusThreadMode::Automatic {
89bc9f8d246datagen2026-10-24 13:40 UTC80
            hide_threaded_events: true,
89bc9f8d246datagen2026-10-24 13:40 UTC81
        },
89bc9f8d246datagen2026-10-24 13:40 UTC82
    };
89bc9f8d246datagen2026-10-24 13:40 UTC83
    let timeline = room
89bc9f8d246datagen2026-10-24 13:40 UTC84
        .timeline_builder()
89bc9f8d246datagen2026-10-24 13:40 UTC85
        .with_focus(focus)
89bc9f8d246datagen2026-10-24 13:40 UTC86
        .build()
89bc9f8d246datagen2026-10-24 13:40 UTC87
        .await
89bc9f8d246datagen2026-10-24 13:40 UTC88
        .map_err(|source| {
89bc9f8d246datagen2026-10-24 13:40 UTC89
            Error::with_source(ErrorKind::Timeline, "building event context failed", source)
89bc9f8d246datagen2026-10-24 13:40 UTC90
        })?;
89bc9f8d246datagen2026-10-24 13:40 UTC91
89bc9f8d246datagen2026-10-24 13:40 UTC92
    if let EventContextCommand::Fetch { .. } = command {
89bc9f8d246datagen2026-10-24 13:40 UTC93
        let raw = raw_context.expect("fetch context response");
89bc9f8d246datagen2026-10-24 13:40 UTC94
        let normalized = normalize_items(timeline.items().await, current_user_id.as_deref());
89bc9f8d246datagen2026-10-24 13:40 UTC95
        let (before, event, after) = split_items(normalized, &event_id);
89bc9f8d246datagen2026-10-24 13:40 UTC96
        if event.is_none() && raw.event.is_none() {
89bc9f8d246datagen2026-10-24 13:40 UTC97
            return Err(Error::new(ErrorKind::EventNotFound, "event was not found"));
89bc9f8d246datagen2026-10-24 13:40 UTC98
        }
89bc9f8d246datagen2026-10-24 13:40 UTC99
        return Ok(crate::CommandOutput::EventContext(EventContext {
89bc9f8d246datagen2026-10-24 13:40 UTC100
            events_before: before,
89bc9f8d246datagen2026-10-24 13:40 UTC101
            event,
89bc9f8d246datagen2026-10-24 13:40 UTC102
            events_after: after,
89bc9f8d246datagen2026-10-24 13:40 UTC103
            start_token: raw.prev_batch_token,
89bc9f8d246datagen2026-10-24 13:40 UTC104
            end_token: raw.next_batch_token,
89bc9f8d246datagen2026-10-24 13:40 UTC105
        }));
89bc9f8d246datagen2026-10-24 13:40 UTC106
    }
89bc9f8d246datagen2026-10-24 13:40 UTC107
89bc9f8d246datagen2026-10-24 13:40 UTC108
    unreachable!("fetch context returned above")
89bc9f8d246datagen2026-10-24 13:40 UTC109
}
89bc9f8d246datagen2026-10-24 13:40 UTC110
89bc9f8d246datagen2026-10-24 13:40 UTC111
async fn execute_page(
89bc9f8d246datagen2026-10-24 13:40 UTC112
    room: Room,
89bc9f8d246datagen2026-10-24 13:40 UTC113
    current_user_id: Option<ruma::OwnedUserId>,
89bc9f8d246datagen2026-10-24 13:40 UTC114
    token: &str,
89bc9f8d246datagen2026-10-24 13:40 UTC115
    limit: u16,
89bc9f8d246datagen2026-10-24 13:40 UTC116
    backwards: bool,
89bc9f8d246datagen2026-10-24 13:40 UTC117
) -> Result<crate::CommandOutput, Error> {
89bc9f8d246datagen2026-10-24 13:40 UTC118
    let mut options = if backwards {
89bc9f8d246datagen2026-10-24 13:40 UTC119
        MessagesOptions::backward()
89bc9f8d246datagen2026-10-24 13:40 UTC120
    } else {
89bc9f8d246datagen2026-10-24 13:40 UTC121
        MessagesOptions::forward()
89bc9f8d246datagen2026-10-24 13:40 UTC122
    };
89bc9f8d246datagen2026-10-24 13:40 UTC123
    options = options.from(token);
89bc9f8d246datagen2026-10-24 13:40 UTC124
    options.limit = ruma::UInt::from(limit);
89bc9f8d246datagen2026-10-24 13:40 UTC125
    let messages = room
89bc9f8d246datagen2026-10-24 13:40 UTC126
        .messages(options)
89bc9f8d246datagen2026-10-24 13:40 UTC127
        .await
89bc9f8d246datagen2026-10-24 13:40 UTC128
        .map_err(|source| Error::from_sdk("fetching event context page failed", source))?;
89bc9f8d246datagen2026-10-24 13:40 UTC129
89bc9f8d246datagen2026-10-24 13:40 UTC130
    let ids: Vec<_> = messages
89bc9f8d246datagen2026-10-24 13:40 UTC131
        .chunk
89bc9f8d246datagen2026-10-24 13:40 UTC132
        .iter()
89bc9f8d246datagen2026-10-24 13:40 UTC133
        .filter_map(|event| event.event_id())
89bc9f8d246datagen2026-10-24 13:40 UTC134
        .collect();
89bc9f8d246datagen2026-10-24 13:40 UTC135
    let normalized = if let Some(target) = ids.first() {
89bc9f8d246datagen2026-10-24 13:40 UTC136
        let focus = TimelineFocus::Event {
89bc9f8d246datagen2026-10-24 13:40 UTC137
            target: target.clone(),
89bc9f8d246datagen2026-10-24 13:40 UTC138
            num_context_events: limit,
89bc9f8d246datagen2026-10-24 13:40 UTC139
            thread_mode: TimelineEventFocusThreadMode::Automatic {
89bc9f8d246datagen2026-10-24 13:40 UTC140
                hide_threaded_events: false,
89bc9f8d246datagen2026-10-24 13:40 UTC141
            },
89bc9f8d246datagen2026-10-24 13:40 UTC142
        };
89bc9f8d246datagen2026-10-24 13:40 UTC143
        let timeline = room
89bc9f8d246datagen2026-10-24 13:40 UTC144
            .timeline_builder()
89bc9f8d246datagen2026-10-24 13:40 UTC145
            .with_focus(focus)
89bc9f8d246datagen2026-10-24 13:40 UTC146
            .build()
89bc9f8d246datagen2026-10-24 13:40 UTC147
            .await
89bc9f8d246datagen2026-10-24 13:40 UTC148
            .map_err(|source| {
89bc9f8d246datagen2026-10-24 13:40 UTC149
                Error::with_source(
89bc9f8d246datagen2026-10-24 13:40 UTC150
                    ErrorKind::Timeline,
89bc9f8d246datagen2026-10-24 13:40 UTC151
                    "building event context page failed",
89bc9f8d246datagen2026-10-24 13:40 UTC152
                    source,
89bc9f8d246datagen2026-10-24 13:40 UTC153
                )
89bc9f8d246datagen2026-10-24 13:40 UTC154
            })?;
89bc9f8d246datagen2026-10-24 13:40 UTC155
        normalize_items(timeline.items().await, current_user_id.as_deref())
89bc9f8d246datagen2026-10-24 13:40 UTC156
    } else {
89bc9f8d246datagen2026-10-24 13:40 UTC157
        Vec::new()
89bc9f8d246datagen2026-10-24 13:40 UTC158
    };
89bc9f8d246datagen2026-10-24 13:40 UTC159
89bc9f8d246datagen2026-10-24 13:40 UTC160
    let mut by_id = std::collections::HashMap::new();
89bc9f8d246datagen2026-10-24 13:40 UTC161
    for item in normalized {
89bc9f8d246datagen2026-10-24 13:40 UTC162
        if let TimelineItem::Event(event) = &item {
89bc9f8d246datagen2026-10-24 13:40 UTC163
            if let Some(event_id) = &event.event_id {
89bc9f8d246datagen2026-10-24 13:40 UTC164
                by_id.insert(event_id.clone(), item);
89bc9f8d246datagen2026-10-24 13:40 UTC165
            }
89bc9f8d246datagen2026-10-24 13:40 UTC166
        }
89bc9f8d246datagen2026-10-24 13:40 UTC167
    }
89bc9f8d246datagen2026-10-24 13:40 UTC168
    let mut events: Vec<_> = ids.iter().filter_map(|id| by_id.remove(id)).collect();
89bc9f8d246datagen2026-10-24 13:40 UTC169
    if backwards {
89bc9f8d246datagen2026-10-24 13:40 UTC170
        events.reverse();
89bc9f8d246datagen2026-10-24 13:40 UTC171
    }
89bc9f8d246datagen2026-10-24 13:40 UTC172
    let next_token = messages.end.clone();
89bc9f8d246datagen2026-10-24 13:40 UTC173
    let end_reached = next_token.is_none();
89bc9f8d246datagen2026-10-24 13:40 UTC174
    let direction = if backwards {
89bc9f8d246datagen2026-10-24 13:40 UTC175
        EventContextDirection::Before
89bc9f8d246datagen2026-10-24 13:40 UTC176
    } else {
89bc9f8d246datagen2026-10-24 13:40 UTC177
        EventContextDirection::After
89bc9f8d246datagen2026-10-24 13:40 UTC178
    };
89bc9f8d246datagen2026-10-24 13:40 UTC179
    Ok(crate::CommandOutput::EventContextPage(EventContextPage {
89bc9f8d246datagen2026-10-24 13:40 UTC180
        direction,
89bc9f8d246datagen2026-10-24 13:40 UTC181
        events,
89bc9f8d246datagen2026-10-24 13:40 UTC182
        next_token,
89bc9f8d246datagen2026-10-24 13:40 UTC183
        end_reached,
89bc9f8d246datagen2026-10-24 13:40 UTC184
    }))
89bc9f8d246datagen2026-10-24 13:40 UTC185
}
89bc9f8d246datagen2026-10-24 13:40 UTC186
89bc9f8d246datagen2026-10-24 13:40 UTC187
fn validate_limit(limit: u16) -> Result<(), Error> {
89bc9f8d246datagen2026-10-24 13:40 UTC188
    if limit > MAX_CONTEXT_LIMIT {
89bc9f8d246datagen2026-10-24 13:40 UTC189
        Err(Error::new(ErrorKind::Invalid, "context limit exceeds 50"))
89bc9f8d246datagen2026-10-24 13:40 UTC190
    } else {
89bc9f8d246datagen2026-10-24 13:40 UTC191
        Ok(())
89bc9f8d246datagen2026-10-24 13:40 UTC192
    }
89bc9f8d246datagen2026-10-24 13:40 UTC193
}
89bc9f8d246datagen2026-10-24 13:40 UTC194
89bc9f8d246datagen2026-10-24 13:40 UTC195
fn normalize_items(
89bc9f8d246datagen2026-10-24 13:40 UTC196
    items: imbl::Vector<Arc<matrix_sdk_ui::timeline::TimelineItem>>,
89bc9f8d246datagen2026-10-24 13:40 UTC197
    current_user_id: Option<&ruma::UserId>,
89bc9f8d246datagen2026-10-24 13:40 UTC198
) -> Vec<TimelineItem> {
89bc9f8d246datagen2026-10-24 13:40 UTC199
    let scope = MediaScope::new();
89bc9f8d246datagen2026-10-24 13:40 UTC200
    normalise_timeline(items.iter(), current_user_id, &scope)
89bc9f8d246datagen2026-10-24 13:40 UTC201
        .items
89bc9f8d246datagen2026-10-24 13:40 UTC202
        .into_iter()
89bc9f8d246datagen2026-10-24 13:40 UTC203
        .collect()
89bc9f8d246datagen2026-10-24 13:40 UTC204
}
89bc9f8d246datagen2026-10-24 13:40 UTC205
89bc9f8d246datagen2026-10-24 13:40 UTC206
fn split_items(
89bc9f8d246datagen2026-10-24 13:40 UTC207
    items: Vec<TimelineItem>,
89bc9f8d246datagen2026-10-24 13:40 UTC208
    event_id: &OwnedEventId,
89bc9f8d246datagen2026-10-24 13:40 UTC209
) -> (Vec<TimelineItem>, Option<TimelineItem>, Vec<TimelineItem>) {
89bc9f8d246datagen2026-10-24 13:40 UTC210
    let items: Vec<_> = items
89bc9f8d246datagen2026-10-24 13:40 UTC211
        .into_iter()
89bc9f8d246datagen2026-10-24 13:40 UTC212
        .filter(|item| matches!(item, TimelineItem::Event(_)))
89bc9f8d246datagen2026-10-24 13:40 UTC213
        .collect();
89bc9f8d246datagen2026-10-24 13:40 UTC214
    let target = items.iter().position(|item| {
89bc9f8d246datagen2026-10-24 13:40 UTC215
        matches!(item, TimelineItem::Event(event) if event.event_id.as_ref() == Some(event_id))
89bc9f8d246datagen2026-10-24 13:40 UTC216
    });
89bc9f8d246datagen2026-10-24 13:40 UTC217
    let Some(index) = target else {
89bc9f8d246datagen2026-10-24 13:40 UTC218
        return (items, None, Vec::new());
89bc9f8d246datagen2026-10-24 13:40 UTC219
    };
89bc9f8d246datagen2026-10-24 13:40 UTC220
    let mut before = items;
89bc9f8d246datagen2026-10-24 13:40 UTC221
    let after = before.split_off(index + 1);
89bc9f8d246datagen2026-10-24 13:40 UTC222
    let event = before.pop();
89bc9f8d246datagen2026-10-24 13:40 UTC223
    (before, event, after)
89bc9f8d246datagen2026-10-24 13:40 UTC224
}