Skip to main content

tuwunel_api/client/read_marker/
read_markers.rs

1use std::collections::BTreeMap;
2
3use axum::extract::State;
4use ruma::{
5	MilliSecondsSinceUnixEpoch,
6	api::client::read_marker::set_read_marker,
7	events::{
8		RoomAccountDataEventType,
9		fully_read::{FullyReadEvent, FullyReadEventContent},
10		receipt::{Receipt, ReceiptEvent, ReceiptEventContent, ReceiptThread, ReceiptType},
11	},
12};
13use tuwunel_core::Result;
14use tuwunel_service::presence::Ping;
15
16use super::{reset_and_refresh_badge, set_private_marker};
17use crate::{ClientIp, Ruma};
18
19/// # `POST /_matrix/client/r0/rooms/{roomId}/read_markers`
20///
21/// Sets different types of read markers.
22///
23/// - Updates fully-read account data event to `fully_read`
24/// - If `read_receipt` is set: Update private marker and public read receipt
25///   EDU
26pub(crate) async fn set_read_marker_route(
27	State(services): State<crate::State>,
28	ClientIp(client): ClientIp,
29	body: Ruma<set_read_marker::v3::Request>,
30) -> Result<set_read_marker::v3::Response> {
31	let sender_user = body.sender_user();
32
33	if let Some(event) = &body.fully_read {
34		let fully_read_event = FullyReadEvent {
35			content: FullyReadEventContent { event_id: event.clone() },
36		};
37
38		services
39			.account_data
40			.update(
41				Some(&body.room_id),
42				sender_user,
43				RoomAccountDataEventType::FullyRead,
44				&serde_json::to_value(fully_read_event)?,
45			)
46			.await
47			.ok();
48	}
49
50	let private_advanced = match &body.private_read_receipt {
51		| None => false,
52		| Some(event) =>
53			set_private_marker(
54				&services,
55				&body.room_id,
56				sender_user,
57				event,
58				&ReceiptThread::Unthreaded,
59			)
60			.await?,
61	};
62
63	let public_advanced = match &body.read_receipt {
64		| None => false,
65		| Some(event) => {
66			let receipt_content = BTreeMap::from_iter([(
67				event.to_owned(),
68				BTreeMap::from_iter([(
69					ReceiptType::Read,
70					BTreeMap::from_iter([(sender_user.to_owned(), Receipt {
71						ts: Some(MilliSecondsSinceUnixEpoch::now()),
72						thread: ReceiptThread::Unthreaded,
73					})]),
74				)]),
75			)]);
76
77			let advanced = services
78				.read_receipt
79				.readreceipt_update(sender_user, &body.room_id, &ReceiptEvent {
80					content: ReceiptEventContent(receipt_content),
81					room_id: body.room_id.clone(),
82				})
83				.await;
84
85			let ping = Ping {
86				device_id: body.sender_device.as_deref(),
87				client_ip: Some(client),
88				appservice: body.appservice_info.as_ref(),
89				..Default::default()
90			};
91
92			services
93				.presence
94				.maybe_ping_presence(sender_user, ping)
95				.await
96				.ok();
97
98			advanced
99		},
100	};
101
102	// Route through the dispatcher so per-thread counts are also cleared;
103	// `/read_markers` predates MSC3771 and carries no thread field.
104	if private_advanced || public_advanced {
105		reset_and_refresh_badge(
106			&services,
107			sender_user,
108			&body.room_id,
109			&ReceiptThread::Unthreaded,
110		)
111		.await;
112	}
113
114	Ok(set_read_marker::v3::Response {})
115}