atagen

macltok

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

Blame: src/normalise/thread.rs

e5c6427adb35atagen2026-10-25 10:23 UTC1
use std::collections::{BTreeMap, HashMap};
e5c6427adb35atagen2026-10-25 10:23 UTC2
e5c6427adb35atagen2026-10-25 10:23 UTC3
use matrix_sdk::{
e5c6427adb35atagen2026-10-25 10:23 UTC4
    Room, RoomState, deserialized_responses::TimelineEvent, serde_helpers::extract_thread_root,
e5c6427adb35atagen2026-10-25 10:23 UTC5
};
e5c6427adb35atagen2026-10-25 10:23 UTC6
use ruma::{
e5c6427adb35atagen2026-10-25 10:23 UTC7
    OwnedEventId, UserId,
e5c6427adb35atagen2026-10-25 10:23 UTC8
    events::receipt::{ReceiptThread, ReceiptType},
e5c6427adb35atagen2026-10-25 10:23 UTC9
};
e5c6427adb35atagen2026-10-25 10:23 UTC10
use serde_json::Value;
e5c6427adb35atagen2026-10-25 10:23 UTC11
e5c6427adb35atagen2026-10-25 10:23 UTC12
use crate::ThreadUnread;
e5c6427adb35atagen2026-10-25 10:23 UTC13
e5c6427adb35atagen2026-10-25 10:23 UTC14
struct Reply {
e5c6427adb35atagen2026-10-25 10:23 UTC15
    event_id: Option<OwnedEventId>,
e5c6427adb35atagen2026-10-25 10:23 UTC16
    own: bool,
e5c6427adb35atagen2026-10-25 10:23 UTC17
    counts: bool,
e5c6427adb35atagen2026-10-25 10:23 UTC18
    notify: bool,
e5c6427adb35atagen2026-10-25 10:23 UTC19
    highlight: bool,
e5c6427adb35atagen2026-10-25 10:23 UTC20
}
e5c6427adb35atagen2026-10-25 10:23 UTC21
e5c6427adb35atagen2026-10-25 10:23 UTC22
impl Reply {
e5c6427adb35atagen2026-10-25 10:23 UTC23
    fn of(event: &TimelineEvent, own_user_id: &UserId) -> Self {
e5c6427adb35atagen2026-10-25 10:23 UTC24
        let own = event.sender().as_deref() == Some(own_user_id);
e5c6427adb35atagen2026-10-25 10:23 UTC25
        let counts = !own && counts_as_unread(event);
e5c6427adb35atagen2026-10-25 10:23 UTC26
        let actions = event.push_actions().unwrap_or_default();
e5c6427adb35atagen2026-10-25 10:23 UTC27
        Self {
e5c6427adb35atagen2026-10-25 10:23 UTC28
            event_id: event.event_id(),
e5c6427adb35atagen2026-10-25 10:23 UTC29
            own,
e5c6427adb35atagen2026-10-25 10:23 UTC30
            counts,
e5c6427adb35atagen2026-10-25 10:23 UTC31
            notify: counts && actions.iter().any(|action| action.should_notify()),
e5c6427adb35atagen2026-10-25 10:23 UTC32
            highlight: counts && actions.iter().any(|action| action.is_highlight()),
e5c6427adb35atagen2026-10-25 10:23 UTC33
        }
e5c6427adb35atagen2026-10-25 10:23 UTC34
    }
e5c6427adb35atagen2026-10-25 10:23 UTC35
}
e5c6427adb35atagen2026-10-25 10:23 UTC36
e5c6427adb35atagen2026-10-25 10:23 UTC37
/// the sdk leaves thread events out of the room's counts once threads are on and keeps none
e5c6427adb35atagen2026-10-25 10:23 UTC38
/// of its own for them. so these are worked out the same way over the events it has in
e5c6427adb35atagen2026-10-25 10:23 UTC39
/// memory, newest first until a receipt for that thread (msc3771), an unthreaded receipt or
e5c6427adb35atagen2026-10-25 10:23 UTC40
/// the user's own reply. a receipt older than memory counts everything in memory, as the
e5c6427adb35atagen2026-10-25 10:23 UTC41
/// sdk's does
e5c6427adb35atagen2026-10-25 10:23 UTC42
pub(crate) async fn thread_unreads(room: &Room) -> BTreeMap<OwnedEventId, ThreadUnread> {
e5c6427adb35atagen2026-10-25 10:23 UTC43
    if room.state() != RoomState::Joined {
e5c6427adb35atagen2026-10-25 10:23 UTC44
        return BTreeMap::new();
e5c6427adb35atagen2026-10-25 10:23 UTC45
    }
e5c6427adb35atagen2026-10-25 10:23 UTC46
    let own_user_id = room.own_user_id();
e5c6427adb35atagen2026-10-25 10:23 UTC47
    let unthreaded = own_receipts(room, ReceiptThread::Unthreaded).await;
e5c6427adb35atagen2026-10-25 10:23 UTC48
    let Ok((cache, _drop_handles)) = room.event_cache().await else {
e5c6427adb35atagen2026-10-25 10:23 UTC49
        return BTreeMap::new();
e5c6427adb35atagen2026-10-25 10:23 UTC50
    };
e5c6427adb35atagen2026-10-25 10:23 UTC51
    let mut threads: HashMap<OwnedEventId, Vec<Reply>> = HashMap::new();
e5c6427adb35atagen2026-10-25 10:23 UTC52
    let scanned = cache
e5c6427adb35atagen2026-10-25 10:23 UTC53
        .rfind_map_event_in_memory_by(|event| {
e5c6427adb35atagen2026-10-25 10:23 UTC54
            let event_id = event.event_id();
e5c6427adb35atagen2026-10-25 10:23 UTC55
            if event_id.is_some_and(|event_id| unthreaded.contains(&event_id)) {
e5c6427adb35atagen2026-10-25 10:23 UTC56
                return Some(());
e5c6427adb35atagen2026-10-25 10:23 UTC57
            }
e5c6427adb35atagen2026-10-25 10:23 UTC58
            if let Some(root) = extract_thread_root(event.raw()) {
e5c6427adb35atagen2026-10-25 10:23 UTC59
                threads
e5c6427adb35atagen2026-10-25 10:23 UTC60
                    .entry(root)
e5c6427adb35atagen2026-10-25 10:23 UTC61
                    .or_default()
e5c6427adb35atagen2026-10-25 10:23 UTC62
                    .push(Reply::of(event, own_user_id));
e5c6427adb35atagen2026-10-25 10:23 UTC63
            }
e5c6427adb35atagen2026-10-25 10:23 UTC64
            None
e5c6427adb35atagen2026-10-25 10:23 UTC65
        })
e5c6427adb35atagen2026-10-25 10:23 UTC66
        .await;
e5c6427adb35atagen2026-10-25 10:23 UTC67
    if let Err(error) = scanned {
e5c6427adb35atagen2026-10-25 10:23 UTC68
        tracing::debug!(room_id = %room.room_id(), %error, "reading events for thread unreads failed");
e5c6427adb35atagen2026-10-25 10:23 UTC69
        return BTreeMap::new();
e5c6427adb35atagen2026-10-25 10:23 UTC70
    }
e5c6427adb35atagen2026-10-25 10:23 UTC71
    let mut unreads = BTreeMap::new();
e5c6427adb35atagen2026-10-25 10:23 UTC72
    for (root, replies) in threads {
e5c6427adb35atagen2026-10-25 10:23 UTC73
        let read = own_receipts(room, ReceiptThread::Thread(root.clone())).await;
e5c6427adb35atagen2026-10-25 10:23 UTC74
        let mut unread = ThreadUnread::default();
e5c6427adb35atagen2026-10-25 10:23 UTC75
        for reply in replies {
e5c6427adb35atagen2026-10-25 10:23 UTC76
            if reply.own
e5c6427adb35atagen2026-10-25 10:23 UTC77
                || reply
e5c6427adb35atagen2026-10-25 10:23 UTC78
                    .event_id
e5c6427adb35atagen2026-10-25 10:23 UTC79
                    .as_ref()
e5c6427adb35atagen2026-10-25 10:23 UTC80
                    .is_some_and(|event_id| read.contains(event_id))
e5c6427adb35atagen2026-10-25 10:23 UTC81
            {
e5c6427adb35atagen2026-10-25 10:23 UTC82
                break;
e5c6427adb35atagen2026-10-25 10:23 UTC83
            }
e5c6427adb35atagen2026-10-25 10:23 UTC84
            unread.messages += u64::from(reply.counts);
e5c6427adb35atagen2026-10-25 10:23 UTC85
            unread.notifications += u64::from(reply.notify);
e5c6427adb35atagen2026-10-25 10:23 UTC86
            unread.mentions += u64::from(reply.highlight);
e5c6427adb35atagen2026-10-25 10:23 UTC87
        }
e5c6427adb35atagen2026-10-25 10:23 UTC88
        if unread != ThreadUnread::default() {
e5c6427adb35atagen2026-10-25 10:23 UTC89
            unreads.insert(root, unread);
e5c6427adb35atagen2026-10-25 10:23 UTC90
        }
e5c6427adb35atagen2026-10-25 10:23 UTC91
    }
e5c6427adb35atagen2026-10-25 10:23 UTC92
    unreads
e5c6427adb35atagen2026-10-25 10:23 UTC93
}
e5c6427adb35atagen2026-10-25 10:23 UTC94
e5c6427adb35atagen2026-10-25 10:23 UTC95
async fn own_receipts(room: &Room, thread: ReceiptThread) -> Vec<OwnedEventId> {
e5c6427adb35atagen2026-10-25 10:23 UTC96
    let mut found = Vec::new();
e5c6427adb35atagen2026-10-25 10:23 UTC97
    for receipt_type in [ReceiptType::Read, ReceiptType::ReadPrivate] {
e5c6427adb35atagen2026-10-25 10:23 UTC98
        match room
e5c6427adb35atagen2026-10-25 10:23 UTC99
            .load_user_receipt(receipt_type, thread.clone(), room.own_user_id())
e5c6427adb35atagen2026-10-25 10:23 UTC100
            .await
e5c6427adb35atagen2026-10-25 10:23 UTC101
        {
e5c6427adb35atagen2026-10-25 10:23 UTC102
            Ok(Some((event_id, _))) => found.push(event_id),
e5c6427adb35atagen2026-10-25 10:23 UTC103
            Ok(None) => {}
e5c6427adb35atagen2026-10-25 10:23 UTC104
            Err(error) => {
e5c6427adb35atagen2026-10-25 10:23 UTC105
                tracing::debug!(room_id = %room.room_id(), %error, "reading a receipt failed");
e5c6427adb35atagen2026-10-25 10:23 UTC106
            }
e5c6427adb35atagen2026-10-25 10:23 UTC107
        }
e5c6427adb35atagen2026-10-25 10:23 UTC108
    }
e5c6427adb35atagen2026-10-25 10:23 UTC109
    found
e5c6427adb35atagen2026-10-25 10:23 UTC110
}
e5c6427adb35atagen2026-10-25 10:23 UTC111
e5c6427adb35atagen2026-10-25 10:23 UTC112
/// the sdk's rule for its room counts, which is private to it
e5c6427adb35atagen2026-10-25 10:23 UTC113
fn counts_as_unread(event: &TimelineEvent) -> bool {
e5c6427adb35atagen2026-10-25 10:23 UTC114
    let raw = event.raw();
e5c6427adb35atagen2026-10-25 10:23 UTC115
    let redacted = raw
e5c6427adb35atagen2026-10-25 10:23 UTC116
        .get_field::<Value>("unsigned")
e5c6427adb35atagen2026-10-25 10:23 UTC117
        .ok()
e5c6427adb35atagen2026-10-25 10:23 UTC118
        .flatten()
e5c6427adb35atagen2026-10-25 10:23 UTC119
        .is_some_and(|unsigned| unsigned.get("redacted_because").is_some());
e5c6427adb35atagen2026-10-25 10:23 UTC120
    let message_like = matches!(
e5c6427adb35atagen2026-10-25 10:23 UTC121
        raw.get_field::<String>("type").ok().flatten().as_deref(),
e5c6427adb35atagen2026-10-25 10:23 UTC122
        Some(
e5c6427adb35atagen2026-10-25 10:23 UTC123
            "m.room.message"
e5c6427adb35atagen2026-10-25 10:23 UTC124
                | "m.room.encrypted"
e5c6427adb35atagen2026-10-25 10:23 UTC125
                | "m.sticker"
e5c6427adb35atagen2026-10-25 10:23 UTC126
                | "m.poll.start"
e5c6427adb35atagen2026-10-25 10:23 UTC127
                | "org.matrix.msc3381.poll.start"
e5c6427adb35atagen2026-10-25 10:23 UTC128
                | "m.poll.end"
e5c6427adb35atagen2026-10-25 10:23 UTC129
                | "org.matrix.msc3381.poll.end"
e5c6427adb35atagen2026-10-25 10:23 UTC130
        )
e5c6427adb35atagen2026-10-25 10:23 UTC131
    );
e5c6427adb35atagen2026-10-25 10:23 UTC132
    message_like && !redacted
e5c6427adb35atagen2026-10-25 10:23 UTC133
}