tuwunel_api_client/read_marker/
mod.rs1mod read_markers;
2mod receipt;
3
4use futures::future::try_join;
5use ruma::{
6 EventId, MilliSecondsSinceUnixEpoch, RoomId, UserId,
7 events::{
8 RoomAccountDataEventType,
9 fully_read::{FullyReadEvent, FullyReadEventContent},
10 receipt::ReceiptThread,
11 },
12};
13use serde_json::to_value;
14use tuwunel_core::{
15 Err, Result, debug, debug_warn, err,
16 utils::result::{LogErr, NotFound},
17};
18use tuwunel_matrix::{Event, PduCount, PduId};
19use tuwunel_service::{Services, rooms::read_receipt::PrivateRead};
20
21pub(crate) use self::{read_markers::set_read_marker_route, receipt::create_receipt_route};
22
23#[tracing::instrument(level = "trace", skip_all)]
24async fn ensure_receipt_visible(
25 services: &Services,
26 room_id: &RoomId,
27 user_id: &UserId,
28 event_id: &EventId,
29) -> Result {
30 let event = services
31 .timeline
32 .get_pdu(event_id)
33 .await
34 .map_err(|_| err!(Request(NotFound("Event not found."))))?;
35
36 if event.room_id() != room_id
37 || !services
38 .state_accessor
39 .user_can_see_event(user_id, &event)
40 .await
41 {
42 return Err!(Request(NotFound("Event not found.")));
43 }
44
45 Ok(())
46}
47
48#[tracing::instrument(level = "trace", skip_all)]
57async fn set_fully_read(
58 services: &Services,
59 room_id: &RoomId,
60 user_id: &UserId,
61 event: &EventId,
62 allow_backward: bool,
63) -> Result {
64 if !allow_backward {
65 let stored = services
66 .account_data
67 .get_room(room_id, user_id, RoomAccountDataEventType::FullyRead)
68 .await
69 .optional()
70 .inspect_err(
71 |error| debug_warn!(%user_id, %room_id, %error, "Unreadable fully-read marker, replacing it"),
72 )
73 .ok()
74 .flatten()
75 .map(|stored: FullyReadEvent| stored.content.event_id);
76
77 if !services
78 .read_receipt
79 .receipt_advanced(stored.as_deref(), event)
80 .await
81 {
82 return Ok(());
83 }
84 }
85
86 let fully_read = FullyReadEvent {
87 content: FullyReadEventContent { event_id: event.to_owned() },
88 };
89
90 services
91 .account_data
92 .update(
93 Some(room_id),
94 user_id,
95 RoomAccountDataEventType::FullyRead,
96 &to_value(fully_read)?,
97 )
98 .await
99}
100
101async fn set_private_marker(
108 services: &Services,
109 room_id: &RoomId,
110 user_id: &UserId,
111 event: &EventId,
112 thread: &ReceiptThread,
113) -> Result<bool> {
114 let (pdu_id, shortroomid) =
115 try_join(services.timeline.get_pdu_id(event), services.short.get_shortroomid(room_id))
116 .await
117 .map_err(|_| err!(Request(NotFound("Event not found."))))?;
118
119 let pdu_id = PduId::from(pdu_id);
120
121 if pdu_id.shortroomid != shortroomid {
122 return Err!(Request(NotFound("Event not found.")));
123 }
124
125 let PduCount::Normal(count) = pdu_id.count else {
126 debug!(%user_id, %room_id, %event, "Skipping private read marker at a backfilled event");
127 return Ok(false);
128 };
129
130 let advanced = services
131 .read_receipt
132 .private_read_set(PrivateRead {
133 room_id,
134 user_id,
135 count,
136 ts: MilliSecondsSinceUnixEpoch::now(),
137 thread,
138 announce: true,
139 })
140 .await;
141
142 Ok(advanced)
143}
144
145async fn reset_and_refresh_badge(
151 services: &Services,
152 user_id: &UserId,
153 room_id: &RoomId,
154 acknowledged: Option<&EventId>,
155 thread: &ReceiptThread,
156) {
157 services
158 .pusher
159 .reset_notification_counts_for_thread(user_id, room_id, acknowledged, thread)
160 .await;
161
162 services
163 .sending
164 .refresh_push_badge(user_id)
165 .await
166 .log_err()
167 .ok();
168}