Skip to main content

matrix_ui_serializable/models/
async_requests.rs

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
24// Re-exports
25pub use matrix_sdk::ruma::api::client::user_directory::search_users::v3::User;
26
27/// Submits a request to the worker thread to be executed asynchronously.
28pub(crate) fn submit_async_request(req: MatrixRequest) {
29    REQUEST_SENDER
30        .get()
31        .unwrap() // this is initialized
32        .send(req)
33        .expect("BUG: async worker task receiver has died!");
34}
35
36/// The set of requests for async work that can be made to the worker thread.
37#[allow(clippy::large_enum_variant)]
38pub enum MatrixRequest {
39    /// Request to paginate the older (or newer) events of a room or thread timeline.
40    PaginateTimeline {
41        timeline_kind: TimelineKind,
42        /// The maximum number of timeline events to fetch in each pagination batch.
43        num_events: u16,
44        direction: PaginationDirection,
45    },
46    /// Request to edit the content of an event in the given room's timeline.
47    EditMessage {
48        timeline_kind: TimelineKind,
49        timeline_event_item_id: TimelineEventItemId,
50        edited_content: EditedContent,
51    },
52    /// Request to fetch the full details of the given event in the given room's timeline.
53    FetchDetailsForEvent {
54        timeline_kind: TimelineKind,
55        event_id: OwnedEventId,
56    },
57    /// Request to create a thread timeline focused on the given thread root event in the given room.
58    CreateThreadTimeline {
59        room_id: OwnedRoomId,
60        thread_root_event_id: OwnedEventId,
61        sender: oneshot::Sender<()>,
62    },
63    /// Request to fetch profile information for all members of a room.
64    /// This can be *very* slow depending on the number of members in the room.
65    SyncRoomMemberList { timeline_kind: TimelineKind },
66    /// Request to join the given room.
67    JoinRoom { room_id: OwnedRoomId },
68    /// Request to leave the given room.
69    LeaveRoom { room_id: OwnedRoomId },
70    /// Request to get the actual list of members in a room.
71    /// This returns the list of members that can be displayed in the UI.
72    GetRoomMembers {
73        timeline_kind: TimelineKind,
74        memberships: RoomMemberships,
75        /// * If `true` (not recommended), only the local cache will be accessed.
76        /// * If `false` (recommended), details will be fetched from the server.
77        local_only: bool,
78    },
79    /// Request to fetch profile information for the given user ID.
80    GetUserProfile {
81        user_id: OwnedUserId,
82        /// * If `Some`, the user is known to be a member of a room, so this will
83        ///   fetch the user's profile from that room's membership info.
84        /// * If `None`, the user's profile info will be fetched from the server
85        ///   in a room-agnostic manner, and no room membership info will be returned.
86        room_id: Option<OwnedRoomId>,
87        /// * If `true` (not recommended), only the local cache will be accessed.
88        /// * If `false` (recommended), details will be fetched from the server.
89        local_only: bool,
90        /// matrix-svelte-client: sender used if a command is awaiting for the
91        /// profile. We send it directly through this channel
92        sender: Option<oneshot::Sender<Option<UserProfile>>>,
93    },
94    /// Request to fetch the number of unread messages in the given room.
95    GetNumberUnreadMessages { timeline_kind: TimelineKind },
96    /// Request to ignore/block or unignore/unblock a user.
97    IgnoreUser {
98        /// Whether to ignore (`true`) or unignore (`false`) the user.
99        ignore: bool,
100        /// The room membership info of the user to (un)ignore.
101        room_member: RoomMember,
102        /// The room ID of the room where the user is a member,
103        /// which is only needed because it isn't present in the `RoomMember` object.
104        room_id: OwnedRoomId,
105    },
106    /// Request to resolve a room alias into a room ID and the servers that know about that room.
107    ResolveRoomAlias(OwnedRoomAliasId),
108    /// Request to fetch media from the server.
109    /// Upon completion of the async media request, the `on_fetched` function
110    /// will be invoked with four arguments: the `destination`, the `media_request`,
111    /// the result of the media fetch, and the `update_sender`.
112    FetchMedia {
113        media_request: MediaRequestParameters,
114        content_sender: oneshot::Sender<Result<Vec<u8>, matrix_sdk::Error>>,
115    },
116    /// Request to send a message to the given room.
117    SendTextMessage {
118        timeline_kind: TimelineKind,
119        message: String,
120        replied_to_id: Option<OwnedEventId>,
121    },
122    /// Sends a notice to the given room that the current user is or is not typing.
123    ///
124    /// This request does not return a response or notify the UI thread, and
125    /// furthermore, there is no need to send a follow-up request to stop typing
126    /// (though you certainly can do so).
127    SendTypingNotice { room_id: OwnedRoomId, typing: bool },
128    /// Subscribe to typing notices for the given room.
129    ///
130    /// This request does not return a response or notify the UI thread.
131    SubscribeToTypingNotices {
132        room_id: OwnedRoomId,
133        /// Whether to subscribe or unsubscribe from typing notices for this room.
134        subscribe: bool,
135    },
136    /// Subscribe to changes in the read receipts of our own user.
137    ///
138    /// This request does not return a response or notify the UI thread.
139    SubscribeToOwnUserReadReceiptsChanged {
140        timeline_kind: TimelineKind,
141        /// Whether to subscribe or unsubscribe.
142        subscribe: bool,
143    },
144    /// Sends a read receipt for the given event in the given room.
145    ReadReceipt {
146        timeline_kind: TimelineKind,
147        event_id: OwnedEventId,
148        receipt_type: ReceiptType,
149    },
150    /// Sends a read receipt in the given room.
151    MarkRoomAsRead { timeline_kind: TimelineKind },
152    /// Sends a request to obtain the power levels for this room.
153    ///
154    /// The response is delivered back to the main UI thread via [`TimelineUpdate::UserPowerLevels`].
155    GetRoomPowerLevels { timeline_kind: TimelineKind },
156    /// Toggles the given reaction to the given event in the given room.
157    ToggleReaction {
158        timeline_kind: TimelineKind,
159        timeline_event_id: TimelineEventItemId,
160        reaction: String,
161    },
162    /// Redacts (deletes) the given event in the given room.
163    #[doc(alias("delete"))]
164    RedactMessage {
165        timeline_kind: TimelineKind,
166        timeline_event_id: TimelineEventItemId,
167        reason: Option<String>,
168    },
169    /// Sends a request to obtain the room's pill link info for the given Matrix ID.
170    ///
171    /// The MatrixLinkPillInfo::Loaded variant is sent back to the main UI thread via.
172    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    /// Create a DM room with a given UserId
182    CreateDMRoom { user_id: OwnedUserId },
183    /// Create a Matrix room
184    CreateRoom {
185        room_name: String,
186        room_avatar: Option<OwnedMxcUri>,
187        invited_user_ids: Vec<OwnedUserId>,
188        topic: Option<String>,
189    },
190    /// Invite a list of users to a room
191    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}
202// Deserialize trait is implemented in models/async_requests.rs
203
204impl<'de> Deserialize<'de> for MatrixRequest {
205    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
206    where
207        D: Deserializer<'de>,
208    {
209        // First deserialize into a generic Value to inspect the structure
210        let value = Value::deserialize(deserializer)?;
211
212        // Extract the "event" field to determine the variant
213        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        // Extract the "payload" field containing the variant data
219        let payload = value
220            .get("payload")
221            .ok_or_else(|| serde::de::Error::missing_field("payload"))?;
222
223        // Match on the event type and deserialize the appropriate variant
224        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                    // We only use remote event_id for now. Transaction (local) ids could be supported in the future
240                    timeline_event_item_id: data.timeline_event_item_id.inner(),
241                    // We only allow editing messages for now.
242                    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            // "syncRoomMemberList" => {
254            //     let data: SyncRoomMemberListPayload =
255            //         serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
256            //     Ok(MatrixRequest::SyncRoomMemberList {
257            //         room_id: data.room_id,
258            //     })
259            // }
260            "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            // "getRoomMembers" => {
275            //     let data: GetRoomMembersPayload =
276            //         serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
277            //     Ok(MatrixRequest::GetRoomMembers {
278            //         room_id: data.room_id,
279            //         memberships: data.memberships,
280            //         local_only: data.local_only,
281            //     })
282            // }
283            "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            // "ignoreUser" => {
301            //     let data: IgnoreUserPayload =
302            //         serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
303            //     Ok(MatrixRequest::IgnoreUser {
304            //         ignore: data.ignore,
305            //         room_member: data.room_member,
306            //         room_id: data.room_id,
307            //     })
308            // }
309            "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                    ), // We only use eventId, not transactions.
379                    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            // "getMatrixRoomLinkPillInfo" => {
392            //     let data: GetMatrixRoomLinkPillInfoPayload =
393            //         serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
394            //     Ok(MatrixRequest::GetMatrixRoomLinkPillInfo {
395            //         matrix_id: data.matrix_id,
396            //         via: data.via,
397            //     })
398            // }
399            "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                    // "syncRoomMemberList",
441                    "joinRoom",
442                    "leaveRoom",
443                    // "getRoomMembers",
444                    "getUserProfile",
445                    "getNumberUnreadMessages",
446                    // "ignoreUser",
447                    "resolveRoomAlias",
448                    "sendTextMessage",
449                    "sendTypingNotice",
450                    "subscribeToTypingNotices",
451                    "subscribeToOwnUserReadReceiptsChanged",
452                    "readReceipt",
453                    "markRoomAsRead",
454                    "getRoomPowerLevels",
455                    "toggleReaction",
456                    "redactMessage",
457                    // "getMatrixRoomLinkPillInfo",
458                    "createDMRoom",
459                    "createRoom",
460                    "inviteUsersInRoom",
461                    "kickOrBanUserFromRoom",
462                ],
463            )),
464        }
465    }
466}
467
468// Helper structs for deserializing payloads
469#[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)]
496// #[serde(rename_all = "camelCase")]
497// struct SyncRoomMemberListPayload {
498//     room_id: OwnedRoomId,
499// }
500
501#[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)]
514// #[serde(rename_all = "camelCase")]
515// struct GetRoomMembersPayload {
516//     room_id: OwnedRoomId,
517//     memberships: RoomMemberships,
518//     local_only: bool,
519// }
520
521#[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)]
537// #[serde(rename_all = "camelCase")]
538// struct IgnoreUserPayload {
539//     ignore: bool,
540//     room_member: RoomMember,
541//     room_id: OwnedRoomId,
542// }
543
544#[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)]
617// #[serde(rename_all = "camelCase")]
618// struct GetMatrixRoomLinkPillInfoPayload {
619//     matrix_id: MatrixId,
620//     via: Vec<OwnedServerName>,
621// }
622
623#[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}