1use matrix_sdk::{
2 OwnedServerName, RoomMemberships,
3 media::MediaRequestParameters,
4 room::{RoomMember, edit::EditedContent},
5 ruma::{
6 OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId,
7 api::client::receipt::create_receipt::v3::ReceiptType,
8 events::room::message::RoomMessageEventContentWithoutRelation, matrix_uri::MatrixId,
9 },
10};
11use matrix_sdk_ui::timeline::TimelineEventItemId;
12use serde::{Deserialize, Deserializer};
13use serde_json::Value;
14use tokio::sync::oneshot;
15
16use crate::{
17 UserProfile,
18 events::timeline::{PaginationDirection, TimelineKind},
19 init::singletons::REQUEST_SENDER,
20 models::profile::ProfileModel,
21 room::frontend_events::timeline_item_id::FrontendTimelineEventItemId,
22};
23
24pub use matrix_sdk::ruma::api::client::user_directory::search_users::v3::User;
26
27pub(crate) fn submit_async_request(req: MatrixRequest) {
29 REQUEST_SENDER
30 .get()
31 .unwrap() .send(req)
33 .expect("BUG: async worker task receiver has died!");
34}
35
36#[allow(clippy::large_enum_variant)]
38pub enum MatrixRequest {
39 PaginateTimeline {
41 timeline_kind: TimelineKind,
42 num_events: u16,
44 direction: PaginationDirection,
45 },
46 EditMessage {
48 timeline_kind: TimelineKind,
49 timeline_event_item_id: TimelineEventItemId,
50 edited_content: EditedContent,
51 },
52 FetchDetailsForEvent {
54 timeline_kind: TimelineKind,
55 event_id: OwnedEventId,
56 },
57 CreateThreadTimeline {
59 room_id: OwnedRoomId,
60 thread_root_event_id: OwnedEventId,
61 sender: oneshot::Sender<()>,
62 },
63 SyncRoomMemberList { timeline_kind: TimelineKind },
66 JoinRoom { room_id: OwnedRoomId },
68 LeaveRoom { room_id: OwnedRoomId },
70 GetRoomMembers {
73 timeline_kind: TimelineKind,
74 memberships: RoomMemberships,
75 local_only: bool,
78 },
79 GetUserProfile {
81 user_id: OwnedUserId,
82 room_id: Option<OwnedRoomId>,
87 local_only: bool,
90 sender: Option<oneshot::Sender<Option<UserProfile>>>,
93 },
94 GetNumberUnreadMessages { timeline_kind: TimelineKind },
96 IgnoreUser {
98 ignore: bool,
100 room_member: RoomMember,
102 room_id: OwnedRoomId,
105 },
106 ResolveRoomAlias(OwnedRoomAliasId),
108 FetchMedia {
113 media_request: MediaRequestParameters,
114 content_sender: oneshot::Sender<Result<Vec<u8>, matrix_sdk::Error>>,
115 },
116 SendTextMessage {
118 timeline_kind: TimelineKind,
119 message: String,
120 replied_to_id: Option<OwnedEventId>,
121 },
122 SendTypingNotice { room_id: OwnedRoomId, typing: bool },
128 SubscribeToTypingNotices {
132 room_id: OwnedRoomId,
133 subscribe: bool,
135 },
136 SubscribeToOwnUserReadReceiptsChanged {
140 timeline_kind: TimelineKind,
141 subscribe: bool,
143 },
144 ReadReceipt {
146 timeline_kind: TimelineKind,
147 event_id: OwnedEventId,
148 receipt_type: ReceiptType,
149 },
150 MarkRoomAsRead { timeline_kind: TimelineKind },
152 GetRoomPowerLevels { timeline_kind: TimelineKind },
156 ToggleReaction {
158 timeline_kind: TimelineKind,
159 timeline_event_id: TimelineEventItemId,
160 reaction: String,
161 },
162 #[doc(alias("delete"))]
164 RedactMessage {
165 timeline_kind: TimelineKind,
166 timeline_event_id: TimelineEventItemId,
167 reason: Option<String>,
168 },
169 GetMatrixRoomLinkPillInfo {
173 matrix_id: MatrixId,
174 via: Vec<OwnedServerName>,
175 },
176 SearchUsers {
177 search_term: String,
178 limit: u64,
179 content_sender: oneshot::Sender<Result<Vec<ProfileModel>, matrix_sdk::Error>>,
180 },
181 CreateDMRoom { user_id: OwnedUserId },
183 CreateRoom {
185 room_name: String,
186 room_avatar: Option<OwnedMxcUri>,
187 invited_user_ids: Vec<OwnedUserId>,
188 topic: Option<String>,
189 },
190 InviteUsersInRoom {
192 room_id: OwnedRoomId,
193 invited_user_ids: Vec<OwnedUserId>,
194 },
195 KickOrBanUserFromRoom {
196 room_id: OwnedRoomId,
197 user_id: OwnedUserId,
198 reason: Option<String>,
199 is_ban: bool,
200 },
201}
202impl<'de> Deserialize<'de> for MatrixRequest {
205 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
206 where
207 D: Deserializer<'de>,
208 {
209 let value = Value::deserialize(deserializer)?;
211
212 let event = value
214 .get("event")
215 .and_then(|v| v.as_str())
216 .ok_or_else(|| serde::de::Error::missing_field("event"))?;
217
218 let payload = value
220 .get("payload")
221 .ok_or_else(|| serde::de::Error::missing_field("payload"))?;
222
223 match event {
225 "paginateTimeline" => {
226 let data: PaginateTimelinePayload =
227 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
228 Ok(MatrixRequest::PaginateTimeline {
229 timeline_kind: get_timeline_kind(data.room_id, data.thread_root_event_id),
230 num_events: data.num_events,
231 direction: data.direction,
232 })
233 }
234 "editMessage" => {
235 let data: EditMessagePayload =
236 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
237 Ok(MatrixRequest::EditMessage {
238 timeline_kind: get_timeline_kind(data.room_id, data.thread_root_event_id),
239 timeline_event_item_id: data.timeline_event_item_id.inner(),
241 edited_content: EditedContent::RoomMessage(data.edited_content),
243 })
244 }
245 "fetchDetailsForEvent" => {
246 let data: FetchDetailsForEventPayload =
247 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
248 Ok(MatrixRequest::FetchDetailsForEvent {
249 timeline_kind: get_timeline_kind(data.room_id, data.thread_root_event_id),
250 event_id: data.event_id,
251 })
252 }
253 "joinRoom" => {
261 let data: JoinRoomPayload =
262 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
263 Ok(MatrixRequest::JoinRoom {
264 room_id: data.room_id,
265 })
266 }
267 "leaveRoom" => {
268 let data: LeaveRoomPayload =
269 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
270 Ok(MatrixRequest::LeaveRoom {
271 room_id: data.room_id,
272 })
273 }
274 "getUserProfile" => {
284 let data: GetUserProfilePayload =
285 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
286 Ok(MatrixRequest::GetUserProfile {
287 user_id: data.user_id,
288 room_id: data.room_id,
289 local_only: data.local_only,
290 sender: None,
291 })
292 }
293 "getNumberUnreadMessages" => {
294 let data: GetNumberUnreadMessagesPayload =
295 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
296 Ok(MatrixRequest::GetNumberUnreadMessages {
297 timeline_kind: get_timeline_kind(data.room_id, data.thread_root_event_id),
298 })
299 }
300 "resolveRoomAlias" => {
310 let alias =
311 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
312 Ok(MatrixRequest::ResolveRoomAlias(alias))
313 }
314 "sendTextMessage" => {
315 let data: SendTextMessagePayload =
316 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
317 Ok(MatrixRequest::SendTextMessage {
318 timeline_kind: get_timeline_kind(data.room_id, data.thread_root_event_id),
319 message: data.message,
320 replied_to_id: data.reply_to_id,
321 })
322 }
323 "sendTypingNotice" => {
324 let data: SendTypingNoticePayload =
325 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
326 Ok(MatrixRequest::SendTypingNotice {
327 room_id: data.room_id,
328 typing: data.typing,
329 })
330 }
331 "subscribeToTypingNotices" => {
332 let data: SubscribeToTypingNoticesPayload =
333 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
334 Ok(MatrixRequest::SubscribeToTypingNotices {
335 room_id: data.room_id,
336 subscribe: data.subscribe,
337 })
338 }
339 "subscribeToOwnUserReadReceiptsChanged" => {
340 let data: SubscribeToOwnUserReadReceiptsChangedPayload =
341 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
342 Ok(MatrixRequest::SubscribeToOwnUserReadReceiptsChanged {
343 timeline_kind: get_timeline_kind(data.room_id, data.thread_root_event_id),
344 subscribe: data.subscribe,
345 })
346 }
347 "readReceipt" => {
348 let data: ReadReceiptPayload =
349 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
350 Ok(MatrixRequest::ReadReceipt {
351 timeline_kind: get_timeline_kind(data.room_id, data.thread_root_event_id),
352 event_id: data.event_id,
353 receipt_type: data.receipt_type,
354 })
355 }
356 "markRoomAsRead" => {
357 let data: MarkRoomAsRead =
358 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
359 Ok(MatrixRequest::MarkRoomAsRead {
360 timeline_kind: get_timeline_kind(data.room_id, data.thread_root_event_id),
361 })
362 }
363 "getRoomPowerLevels" => {
364 let data: GetRoomPowerLevelsPayload =
365 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
366 Ok(MatrixRequest::GetRoomPowerLevels {
367 timeline_kind: get_timeline_kind(data.room_id, data.thread_root_event_id),
368 })
369 }
370 "toggleReaction" => {
371 let data: ToggleReactionPayload =
372 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
373 Ok(MatrixRequest::ToggleReaction {
374 timeline_kind: get_timeline_kind(data.room_id, data.thread_root_event_id),
375 timeline_event_id: TimelineEventItemId::EventId(
376 OwnedEventId::try_from(data.timeline_event_id)
377 .expect("Frontend sent incorrect event id"),
378 ), reaction: data.reaction,
380 })
381 }
382 "redactMessage" => {
383 let data: RedactMessagePayload =
384 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
385 Ok(MatrixRequest::RedactMessage {
386 timeline_kind: get_timeline_kind(data.room_id, data.thread_root_event_id),
387 timeline_event_id: TimelineEventItemId::EventId(data.timeline_event_id),
388 reason: data.reason,
389 })
390 }
391 "createRoom" => {
400 let data: CreateRoomPayload =
401 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
402 Ok(MatrixRequest::CreateRoom {
403 room_name: data.room_name,
404 room_avatar: data.room_avatar,
405 invited_user_ids: data.invited_user_ids,
406 topic: data.topic,
407 })
408 }
409 "createDMRoom" => {
410 let data: CreateDMRoomPayload =
411 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
412 Ok(MatrixRequest::CreateDMRoom {
413 user_id: data.user_id,
414 })
415 }
416 "inviteUsersInRoom" => {
417 let data: InviteUsersInRoomPayload =
418 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
419 Ok(MatrixRequest::InviteUsersInRoom {
420 room_id: data.room_id,
421 invited_user_ids: data.invited_user_ids,
422 })
423 }
424 "kickOrBanUserFromRoom" => {
425 let data: KickOrBanUserFromRoomPayload =
426 serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
427 Ok(MatrixRequest::KickOrBanUserFromRoom {
428 room_id: data.room_id,
429 user_id: data.user_id,
430 reason: data.reason,
431 is_ban: data.is_ban,
432 })
433 }
434 _ => Err(serde::de::Error::unknown_variant(
435 event,
436 &[
437 "paginateTimeline",
438 "editMessage",
439 "fetchDetailsForEvent",
440 "joinRoom",
442 "leaveRoom",
443 "getUserProfile",
445 "getNumberUnreadMessages",
446 "resolveRoomAlias",
448 "sendTextMessage",
449 "sendTypingNotice",
450 "subscribeToTypingNotices",
451 "subscribeToOwnUserReadReceiptsChanged",
452 "readReceipt",
453 "markRoomAsRead",
454 "getRoomPowerLevels",
455 "toggleReaction",
456 "redactMessage",
457 "createDMRoom",
459 "createRoom",
460 "inviteUsersInRoom",
461 "kickOrBanUserFromRoom",
462 ],
463 )),
464 }
465 }
466}
467
468#[derive(Deserialize)]
470#[serde(rename_all = "camelCase")]
471struct PaginateTimelinePayload {
472 room_id: OwnedRoomId,
473 thread_root_event_id: Option<OwnedEventId>,
474 num_events: u16,
475 direction: PaginationDirection,
476}
477
478#[derive(Deserialize)]
479#[serde(rename_all = "camelCase")]
480struct EditMessagePayload {
481 room_id: OwnedRoomId,
482 thread_root_event_id: Option<OwnedEventId>,
483 timeline_event_item_id: FrontendTimelineEventItemId,
484 edited_content: RoomMessageEventContentWithoutRelation,
485}
486
487#[derive(Deserialize)]
488#[serde(rename_all = "camelCase")]
489struct FetchDetailsForEventPayload {
490 room_id: OwnedRoomId,
491 thread_root_event_id: Option<OwnedEventId>,
492 event_id: OwnedEventId,
493}
494
495#[derive(Deserialize)]
502#[serde(rename_all = "camelCase")]
503struct JoinRoomPayload {
504 room_id: OwnedRoomId,
505}
506
507#[derive(Deserialize)]
508#[serde(rename_all = "camelCase")]
509struct LeaveRoomPayload {
510 room_id: OwnedRoomId,
511}
512
513#[derive(Deserialize)]
522#[serde(rename_all = "camelCase")]
523struct GetUserProfilePayload {
524 user_id: OwnedUserId,
525 room_id: Option<OwnedRoomId>,
526 local_only: bool,
527}
528
529#[derive(Deserialize)]
530#[serde(rename_all = "camelCase")]
531struct GetNumberUnreadMessagesPayload {
532 room_id: OwnedRoomId,
533 thread_root_event_id: Option<OwnedEventId>,
534}
535
536#[derive(Deserialize)]
545#[serde(rename_all = "camelCase")]
546struct SendTextMessagePayload {
547 room_id: OwnedRoomId,
548 thread_root_event_id: Option<OwnedEventId>,
549 message: String,
550 reply_to_id: Option<OwnedEventId>,
551}
552
553#[derive(Deserialize)]
554#[serde(rename_all = "camelCase")]
555struct SendTypingNoticePayload {
556 room_id: OwnedRoomId,
557 typing: bool,
558}
559
560#[derive(Deserialize)]
561#[serde(rename_all = "camelCase")]
562struct SubscribeToTypingNoticesPayload {
563 room_id: OwnedRoomId,
564 subscribe: bool,
565}
566
567#[derive(Deserialize)]
568#[serde(rename_all = "camelCase")]
569struct SubscribeToOwnUserReadReceiptsChangedPayload {
570 room_id: OwnedRoomId,
571 thread_root_event_id: Option<OwnedEventId>,
572 subscribe: bool,
573}
574
575#[derive(Deserialize)]
576#[serde(rename_all = "camelCase")]
577struct ReadReceiptPayload {
578 room_id: OwnedRoomId,
579 thread_root_event_id: Option<OwnedEventId>,
580 event_id: OwnedEventId,
581 receipt_type: ReceiptType,
582}
583
584#[derive(Deserialize)]
585#[serde(rename_all = "camelCase")]
586struct MarkRoomAsRead {
587 room_id: OwnedRoomId,
588 thread_root_event_id: Option<OwnedEventId>,
589}
590
591#[derive(Deserialize)]
592#[serde(rename_all = "camelCase")]
593struct GetRoomPowerLevelsPayload {
594 room_id: OwnedRoomId,
595 thread_root_event_id: Option<OwnedEventId>,
596}
597
598#[derive(Deserialize)]
599#[serde(rename_all = "camelCase")]
600struct ToggleReactionPayload {
601 room_id: OwnedRoomId,
602 thread_root_event_id: Option<OwnedEventId>,
603 timeline_event_id: String,
604 reaction: String,
605}
606
607#[derive(Deserialize)]
608#[serde(rename_all = "camelCase")]
609struct RedactMessagePayload {
610 room_id: OwnedRoomId,
611 thread_root_event_id: Option<OwnedEventId>,
612 timeline_event_id: OwnedEventId,
613 reason: Option<String>,
614}
615
616#[derive(Deserialize)]
624#[serde(rename_all = "camelCase")]
625struct CreateDMRoomPayload {
626 user_id: OwnedUserId,
627}
628
629#[derive(Deserialize)]
630#[serde(rename_all = "camelCase")]
631struct CreateRoomPayload {
632 room_name: String,
633 room_avatar: Option<OwnedMxcUri>,
634 invited_user_ids: Vec<OwnedUserId>,
635 topic: Option<String>,
636}
637
638#[derive(Deserialize)]
639#[serde(rename_all = "camelCase")]
640struct InviteUsersInRoomPayload {
641 room_id: OwnedRoomId,
642 invited_user_ids: Vec<OwnedUserId>,
643}
644
645#[derive(Deserialize)]
646#[serde(rename_all = "camelCase")]
647struct KickOrBanUserFromRoomPayload {
648 room_id: OwnedRoomId,
649 user_id: OwnedUserId,
650 reason: Option<String>,
651 is_ban: bool,
652}
653
654pub(crate) fn get_timeline_kind(room_id: OwnedRoomId, root: Option<OwnedEventId>) -> TimelineKind {
655 if let Some(thread_root_event_id) = root {
656 TimelineKind::Thread {
657 room_id,
658 thread_root_event_id,
659 }
660 } else {
661 TimelineKind::MainRoom { room_id }
662 }
663}