1use ruma::{
19 events::{relation::BundledThread, AnyMessageLikeEvent, AnySyncTimelineEvent},
20 serde::Raw,
21 OwnedEventId,
22};
23use serde::Deserialize;
24
25use crate::deserialized_responses::{ThreadSummary, ThreadSummaryStatus};
26
27#[derive(Deserialize)]
28enum RelationsType {
29 #[serde(rename = "m.thread")]
30 Thread,
31}
32
33#[derive(Deserialize)]
34struct RelatesTo {
35 #[serde(rename = "rel_type")]
36 rel_type: RelationsType,
37 #[serde(rename = "event_id")]
38 event_id: Option<OwnedEventId>,
39}
40
41#[allow(missing_debug_implementations)]
42#[derive(Deserialize)]
43struct SimplifiedContent {
44 #[serde(rename = "m.relates_to")]
45 relates_to: Option<RelatesTo>,
46}
47
48pub fn extract_thread_root(event: &Raw<AnySyncTimelineEvent>) -> Option<OwnedEventId> {
56 let relates_to = event.get_field::<SimplifiedContent>("content").ok().flatten()?.relates_to?;
57 match relates_to.rel_type {
58 RelationsType::Thread => relates_to.event_id,
59 }
60}
61
62#[allow(missing_debug_implementations)]
63#[derive(Deserialize)]
64struct Relations {
65 #[serde(rename = "m.thread")]
66 thread: Option<Box<BundledThread>>,
67}
68
69#[allow(missing_debug_implementations)]
70#[derive(Deserialize)]
71struct Unsigned {
72 #[serde(rename = "m.relations")]
73 relations: Option<Relations>,
74}
75
76pub fn extract_bundled_thread_summary(
78 event: &Raw<AnySyncTimelineEvent>,
79) -> (ThreadSummaryStatus, Option<Raw<AnyMessageLikeEvent>>) {
80 match event.get_field::<Unsigned>("unsigned") {
81 Ok(Some(Unsigned { relations: Some(Relations { thread: Some(bundled_thread) }) })) => {
82 let count = bundled_thread.count.try_into().unwrap_or(u32::MAX);
86
87 let latest_reply =
88 bundled_thread.latest_event.get_field::<OwnedEventId>("event_id").ok().flatten();
89
90 (
91 ThreadSummaryStatus::Some(ThreadSummary { num_replies: count, latest_reply }),
92 Some(bundled_thread.latest_event),
93 )
94 }
95 Ok(_) => (ThreadSummaryStatus::None, None),
96 Err(_) => (ThreadSummaryStatus::Unknown, None),
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use assert_matches::assert_matches;
103 use ruma::{event_id, serde::Raw};
104 use serde_json::json;
105
106 use super::extract_thread_root;
107 use crate::{
108 deserialized_responses::{ThreadSummary, ThreadSummaryStatus},
109 serde_helpers::extract_bundled_thread_summary,
110 };
111
112 #[test]
113 fn test_extract_thread_root() {
114 let thread_root = event_id!("$thread_root_event_id:example.com");
119 let event = Raw::new(&json!({
120 "event_id": "$eid:example.com",
121 "type": "m.room.message",
122 "sender": "@alice:example.com",
123 "origin_server_ts": 42,
124 "content": {
125 "body": "Hello, world!",
126 "m.relates_to": {
127 "rel_type": "m.thread",
128 "event_id": thread_root,
129 }
130 }
131 }))
132 .unwrap()
133 .cast();
134
135 let observed_thread_root = extract_thread_root(&event);
136 assert_eq!(observed_thread_root.as_deref(), Some(thread_root));
137
138 let event = Raw::new(&json!({
141 "event_id": "$eid:example.com",
142 "type": "m.room.message",
143 "sender": "@alice:example.com",
144 "origin_server_ts": 42,
145 }))
146 .unwrap()
147 .cast();
148
149 let observed_thread_root = extract_thread_root(&event);
150 assert_matches!(observed_thread_root, None);
151
152 let event = Raw::new(&json!({
154 "event_id": "$eid:example.com",
155 "type": "m.room.message",
156 "sender": "@alice:example.com",
157 "origin_server_ts": 42,
158 "content": {
159 "body": "Hello, world!",
160 }
161 }))
162 .unwrap()
163 .cast();
164
165 let observed_thread_root = extract_thread_root(&event);
166 assert_matches!(observed_thread_root, None);
167
168 let event = Raw::new(&json!({
170 "event_id": "$eid:example.com",
171 "type": "m.room.message",
172 "sender": "@alice:example.com",
173 "origin_server_ts": 42,
174 "content": {
175 "body": "Hello, world!",
176 "m.relates_to": {
177 "rel_type": "m.reference",
178 "event_id": "$referenced_event_id:example.com",
179 }
180 }
181 }))
182 .unwrap()
183 .cast();
184
185 let observed_thread_root = extract_thread_root(&event);
186 assert_matches!(observed_thread_root, None);
187 }
188
189 #[test]
190 fn test_extract_bundled_thread_summary() {
191 let event = Raw::new(&json!({
193 "event_id": "$eid:example.com",
194 "type": "m.room.message",
195 "sender": "@alice:example.com",
196 "origin_server_ts": 42,
197 "content": {
198 "body": "Hello, world!",
199 },
200 "unsigned": {
201 "m.relations": {
202 "m.thread": {
203 "latest_event": {
204 "event_id": "$latest_event:example.com",
205 "type": "m.room.message",
206 "sender": "@bob:example.com",
207 "origin_server_ts": 42,
208 "content": {
209 "body": "Hello to you too!",
210 }
211 },
212 "count": 2,
213 "current_user_participated": true,
214 }
215 }
216 }
217 }))
218 .unwrap()
219 .cast();
220
221 assert_matches!(
222 extract_bundled_thread_summary(&event),
223 (ThreadSummaryStatus::Some(ThreadSummary { .. }), Some(..))
224 );
225
226 let event = Raw::new(&json!({
228 "event_id": "$eid:example.com",
229 "type": "m.room.message",
230 "sender": "@alice:example.com",
231 "origin_server_ts": 42,
232 }))
233 .unwrap()
234 .cast();
235
236 assert_matches!(extract_bundled_thread_summary(&event), (ThreadSummaryStatus::None, None));
237
238 let event = Raw::new(&json!({
240 "event_id": "$eid:example.com",
241 "type": "m.room.message",
242 "sender": "@alice:example.com",
243 "origin_server_ts": 42,
244 "content": {
245 "body": "Bonjour, monde!",
246 },
247 "unsigned": {
248 "m.relations": {
249 "m.replace":
250 {
251 "event_id": "$update:example.com",
252 "type": "m.room.message",
253 "sender": "@alice:example.com",
254 "origin_server_ts": 43,
255 "content": {
256 "body": "* Hello, world!",
257 }
258 },
259 }
260 }
261 }))
262 .unwrap()
263 .cast();
264
265 assert_matches!(extract_bundled_thread_summary(&event), (ThreadSummaryStatus::None, None));
266
267 let event = Raw::new(&json!({
270 "event_id": "$eid:example.com",
271 "type": "m.room.message",
272 "sender": "@alice:example.com",
273 "origin_server_ts": 42,
274 "unsigned": {
275 "m.relations": {
276 "m.thread": {
277 }
279 }
280 }
281 }))
282 .unwrap()
283 .cast();
284
285 assert_matches!(
286 extract_bundled_thread_summary(&event),
287 (ThreadSummaryStatus::Unknown, None)
288 );
289 }
290}