palpo_core/federation/
membership.rs

1//! Room membership endpoints.
2
3use itertools::Itertools;
4use reqwest::Url;
5use salvo::prelude::*;
6use serde::{Deserialize, Serialize};
7
8use crate::events::{AnyStrippedStateEvent, StateEventType, room::member::RoomMemberEventContent};
9use crate::identifiers::*;
10use crate::sending::{SendRequest, SendResult};
11use crate::{RawJsonValue, UnixMillis, serde::RawJson};
12
13pub fn invite_user_request_v2(
14    origin: &str,
15    args: InviteUserReqArgs,
16    body: InviteUserReqBodyV2,
17) -> SendResult<SendRequest> {
18    let url = Url::parse(&format!(
19        "{origin}/_matrix/federation/v2/invite/{}/{}",
20        args.room_id, args.event_id
21    ))?;
22    crate::sending::put(url).stuff(body)
23}
24#[derive(ToParameters, Deserialize, Debug)]
25pub struct InviteUserReqArgs {
26    /// The room ID that is about to be joined.
27    ///
28    /// Do not use this. Instead, use the `room_id` field inside the PDU.
29    #[salvo(parameter(parameter_in = Path))]
30    pub room_id: OwnedRoomId,
31
32    /// The event ID for the join event.
33    #[salvo(parameter(parameter_in = Path))]
34    pub event_id: OwnedEventId,
35}
36
37#[derive(ToSchema, Deserialize, Serialize, Debug)]
38pub struct InviteUserReqBodyV2 {
39    // /// The room ID that the user is being invited to.
40    // #[salvo(parameter(parameter_in = Path))]
41    // pub room_id: OwnedRoomId,
42
43    // /// The event ID for the invite event, generated by the inviting server.
44    // #[salvo(parameter(parameter_in = Path))]
45    // pub event_id: OwnedEventId,
46    /// The version of the room where the user is being invited to.
47    pub room_version: RoomVersionId,
48
49    /// The invite event which needs to be signed.
50    #[salvo(schema(value_type = Object, additional_properties = true))]
51    pub event: Box<RawJsonValue>,
52
53    /// An optional list of simplified events to help the receiver of the invite identify the room.
54    pub invite_room_state: Vec<RawJson<AnyStrippedStateEvent>>,
55
56    /// An optional list of servers the invited homeserver should attempt to join or leave via,
57    /// according to [MSC4125](https://github.com/matrix-org/matrix-spec-proposals/pull/4125).
58    ///
59    /// If present, it must not be empty.
60    #[serde(skip_serializing_if = "Option::is_none", rename = "org.matrix.msc4125.via")]
61    pub via: Option<Vec<OwnedServerName>>,
62}
63crate::json_body_modifier!(InviteUserReqBodyV2);
64
65#[derive(ToSchema, Deserialize, Debug)]
66pub struct InviteUserReqBodyV1 {
67    /// The matrix ID of the user who sent the original `m.room.third_party_invite`.
68    pub sender: OwnedUserId,
69
70    /// The name of the inviting homeserver.
71    pub origin: OwnedServerName,
72
73    /// A timestamp added by the inviting homeserver.
74    pub origin_server_ts: UnixMillis,
75
76    /// The value `m.room.member`.
77    #[serde(rename = "type")]
78    pub kind: StateEventType,
79
80    /// The user ID of the invited member.
81    pub state_key: OwnedUserId,
82
83    /// The content of the event.
84    pub content: RoomMemberEventContent,
85
86    /// Information included alongside the event that is not signed.
87    #[serde(default, skip_serializing_if = "UnsignedEventContent::is_empty")]
88    pub unsigned: UnsignedEventContentV1,
89}
90
91#[derive(ToSchema, Serialize, Deserialize, Debug)]
92pub struct InviteUserResBodyV2 {
93    /// The signed invite event.
94    #[salvo(schema(value_type = Object))]
95    pub event: Box<RawJsonValue>,
96}
97
98#[derive(ToSchema, Serialize, Debug)]
99pub struct InviteUserResBodyV1 {
100    /// The signed invite event.
101    #[serde(with = "crate::federation::serde::v1_pdu")]
102    #[salvo(schema(value_type = Object))]
103    pub event: Box<RawJsonValue>,
104}
105
106#[derive(ToSchema, Deserialize, Serialize, Debug)]
107#[salvo(schema(value_type = Object))]
108pub struct SendJoinReqBody(
109    /// The invite event which needs to be signed.
110    pub Box<RawJsonValue>,
111);
112crate::json_body_modifier!(SendJoinReqBody);
113
114#[derive(ToSchema, Deserialize, Serialize, Debug)]
115pub struct SendJoinResBodyV2(
116    /// The signed invite event.
117    pub RoomStateV2,
118);
119
120#[derive(ToSchema, Serialize, Debug)]
121pub struct SendJoinResBodyV1(
122    /// Full state of the room.
123    pub RoomStateV1,
124);
125
126impl SendJoinResBodyV1 {
127    /// Creates a new `Response` with the given room state.
128    pub fn new(room_state: RoomStateV1) -> Self {
129        Self(room_state)
130    }
131}
132
133/// Full state of the room.
134#[derive(ToSchema, Deserialize, Serialize, Clone, Debug)]
135pub struct RoomStateV2 {
136    /// Whether `m.room.member` events have been omitted from `state`.
137    ///
138    /// Defaults to `false`.
139    #[serde(default, skip_serializing_if = "crate::serde::is_default")]
140    pub members_omitted: bool,
141
142    /// The full set of authorization events that make up the state of the room,
143    /// and their authorization events, recursively.
144    ///
145    /// If the request had `omit_members` set to `true`, then any events that are returned in
146    /// `state` may be omitted from `auth_chain`, whether or not membership events are omitted
147    /// from `state`.
148    #[salvo(schema(value_type = Vec<Object>))]
149    pub auth_chain: Vec<Box<RawJsonValue>>,
150
151    /// The room state.
152    ///
153    /// If the request had `omit_members` set to `true`, events of type `m.room.member` may be
154    /// omitted from the response to reduce the size of the response. If this is done,
155    /// `members_omitted` must be set to `true`.
156    #[salvo(schema(value_type = Object))]
157    pub state: Vec<Box<RawJsonValue>>,
158
159    /// The signed copy of the membership event sent to other servers by the
160    /// resident server, including the resident server's signature.
161    ///
162    /// Required if the room version supports restricted join rules.
163    #[serde(skip_serializing_if = "Option::is_none")]
164    #[salvo(schema(value_type = Object))]
165    pub event: Option<Box<RawJsonValue>>,
166
167    /// A list of the servers active in the room (ie, those with joined members) before the join.
168    ///
169    /// Required if `members_omitted` is set to `true`.
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub servers_in_room: Option<Vec<String>>,
172}
173
174impl RoomStateV2 {
175    /// Creates an empty `RoomState` with the given `origin`.
176    ///
177    /// With the `unstable-unspecified` feature, this method doesn't take any parameters.
178    /// See [matrix-spec#374](https://github.com/matrix-org/matrix-spec/issues/374).
179    pub fn new() -> Self {
180        Self {
181            auth_chain: Vec::new(),
182            state: Vec::new(),
183            event: None,
184            members_omitted: false,
185            servers_in_room: None,
186        }
187    }
188}
189
190/// Full state of the room.
191#[derive(ToSchema, Deserialize, Serialize, Clone, Debug)]
192pub struct RoomStateV1 {
193    /// The full set of authorization events that make up the state of the room,
194    /// and their authorization events, recursively.
195    #[salvo(schema(value_type = Vec<Object>))]
196    pub auth_chain: Vec<Box<RawJsonValue>>,
197
198    /// The room state.
199    #[salvo(schema(value_type = Vec<Object>))]
200    pub state: Vec<Box<RawJsonValue>>,
201
202    /// The signed copy of the membership event sent to other servers by the
203    /// resident server, including the resident server's signature.
204    ///
205    /// Required if the room version supports restricted join rules.
206    #[serde(skip_serializing_if = "Option::is_none")]
207    #[salvo(schema(value_type = Vec<Object>))]
208    pub event: Option<Box<RawJsonValue>>,
209}
210impl RoomStateV1 {
211    /// Creates an empty `RoomState` with the given `origin`.
212    ///
213    /// With the `unstable-unspecified` feature, this method doesn't take any parameters.
214    /// See [matrix-spec#374](https://github.com/matrix-org/matrix-spec/issues/374).
215    pub fn new() -> Self {
216        Self {
217            auth_chain: Vec::new(),
218            state: Vec::new(),
219            event: None,
220        }
221    }
222}
223
224/// Information included alongside an event that is not signed.
225#[derive(ToSchema, Clone, Debug, Default, Serialize, Deserialize)]
226pub struct UnsignedEventContentV1 {
227    /// An optional list of simplified events to help the receiver of the invite identify the room.
228    /// The recommended events to include are the join rules, canonical alias, avatar, and name of
229    /// the room.
230    #[serde(skip_serializing_if = "<[_]>::is_empty")]
231    #[salvo(schema(value_type = Vec<Object>))]
232    pub invite_room_state: Vec<RawJson<AnyStrippedStateEvent>>,
233}
234
235impl UnsignedEventContentV1 {
236    /// Creates an empty `UnsignedEventContent`.
237    pub fn new() -> Self {
238        Default::default()
239    }
240
241    /// Checks whether all of the fields are empty.
242    pub fn is_empty(&self) -> bool {
243        self.invite_room_state.is_empty()
244    }
245}
246
247// const METADATA: Metadata = metadata! {
248//     method: GET,
249//     rate_limited: false,
250//     authentication: ServerSignatures,
251//     history: {
252//         1.0 => "/_matrix/federation/v1/make_leave/:room_id/:user_id",
253//     }
254// };
255
256pub fn make_leave_request(origin: &str, room_id: &RoomId, user_id: &UserId) -> SendResult<SendRequest> {
257    let url = Url::parse(&format!(
258        "{origin}/_matrix/federation/v1/make_leave/{room_id}/{user_id}"
259    ))?;
260    Ok(crate::sending::get(url))
261}
262#[derive(ToParameters, Deserialize, Debug)]
263pub struct MakeLeaveReqArgs {
264    /// Room in which the event to be reported is located.
265    #[salvo(parameter(parameter_in = Path))]
266    pub room_id: OwnedRoomId,
267
268    /// Event to report.
269    #[salvo(parameter(parameter_in = Path))]
270    pub user_id: OwnedUserId,
271}
272
273/// Response type for the `get_leave_event` endpoint.
274#[derive(ToSchema, Serialize, Deserialize, Debug)]
275
276pub struct MakeLeaveResBody {
277    /// The version of the room where the server is trying to leave.
278    ///
279    /// If not provided, the room version is assumed to be either "1" or "2".
280    pub room_version: Option<RoomVersionId>,
281
282    /// An unsigned template event.
283    ///
284    /// Note that events have a different format depending on the room version - check the room
285    /// version specification for precise event formats.
286    #[salvo(schema(value_type = Object, additional_properties = true))]
287    pub event: Box<RawJsonValue>,
288}
289impl MakeLeaveResBody {
290    /// Creates a new `Response` with:
291    /// * the version of the room where the server is trying to leave.
292    /// * an unsigned template event.
293    pub fn new(room_version: Option<RoomVersionId>, event: Box<RawJsonValue>) -> Self {
294        Self { room_version, event }
295    }
296}
297
298// const METADATA: Metadata = metadata! {
299//     method: PUT,
300//     rate_limited: false,
301//     authentication: ServerSignatures,
302//     history: {
303//         1.0 => "/_matrix/federation/v2/send_leave/:room_id/:event_id",
304//     }
305// };
306
307pub fn send_leave_request_v2(
308    origin: &str,
309    args: SendLeaveReqArgsV2,
310    body: SendLeaveReqBody,
311) -> SendResult<SendRequest> {
312    let url = Url::parse(&format!(
313        "{origin}/_matrix/federation/v2/send_leave/{}/{}",
314        args.room_id, args.event_id
315    ))?;
316    crate::sending::put(url).stuff(body)
317}
318#[derive(ToParameters, Deserialize, Serialize, Debug)]
319pub struct SendLeaveReqArgsV2 {
320    /// The room ID that is about to be left.
321    ///
322    /// Do not use this. Instead, use the `room_id` field inside the PDU.
323    #[salvo(parameter(parameter_in = Path))]
324    pub room_id: OwnedRoomId,
325
326    /// The event ID for the leave event.
327    #[salvo(parameter(parameter_in = Path))]
328    pub event_id: OwnedEventId,
329}
330/// Request type for the `create_leave_event` endpoint.
331#[derive(ToSchema, Deserialize, Serialize, Debug)]
332#[salvo(schema(value_type = Object))]
333pub struct SendLeaveReqBody(
334    /// The PDU.
335    pub Box<RawJsonValue>,
336);
337crate::json_body_modifier!(SendLeaveReqBody);
338
339/// `PUT /_matrix/federation/*/send_join/{room_id}/{event_id}`
340///
341/// Send a join event to a resident server.
342
343// const METADATA: Metadata = metadata! {
344//     method: PUT,
345//     rate_limited: false,
346//     authentication: ServerSignatures,
347//     history: {
348//         1.0 => "/_matrix/federation/v2/send_join/:room_id/:event_id",
349//     }
350// };
351
352pub fn send_join_request(origin: &str, args: SendJoinArgs, body: SendJoinReqBody) -> SendResult<SendRequest> {
353    let url = Url::parse(&format!(
354        "{origin}/_matrix/federation/v2/send_join/{}/{}?omit_members={}",
355        &args.room_id, &args.event_id, args.omit_members
356    ))?;
357    crate::sending::put(url).stuff(body)
358}
359/// Request type for the `create_join_event` endpoint.
360#[derive(ToParameters, Deserialize, Debug)]
361pub struct SendJoinArgs {
362    /// The room ID that is about to be joined.
363    ///
364    /// Do not use this. Instead, use the `room_id` field inside the PDU.
365    #[salvo(parameter(parameter_in = Path))]
366    pub room_id: OwnedRoomId,
367
368    /// The event ID for the join event.
369    #[salvo(parameter(parameter_in = Path))]
370    pub event_id: OwnedEventId,
371
372    /// Indicates whether the calling server can accept a reduced response.
373    ///
374    /// If `true`, membership events are omitted from `state` and redundant events are omitted from
375    /// `auth_chain` in the response.
376    ///
377    /// If the room to be joined has no `m.room.name` nor `m.room.canonical_alias` events in its
378    /// current state, the resident server should determine the room members who would be
379    /// included in the `m.heroes` property of the room summary as defined in the [Client-Server
380    /// `/sync` response]. The resident server should include these members' membership events in
381    /// the response `state` field, and include the auth chains for these membership events in
382    /// the response `auth_chain` field.
383    ///
384    /// [Client-Server `/sync` response]: https://spec.matrix.org/latest/client-server-api/#get_matrixclientv3sync
385    #[salvo(parameter(parameter_in = Query))]
386    #[serde(default, skip_serializing_if = "crate::serde::is_default")]
387    pub omit_members: bool,
388}
389
390// const METADATA: Metadata = metadata! {
391//     method: GET,
392//     rate_limited: false,
393//     authentication: ServerSignatures,
394//     history: {
395//         1.0 => "/_matrix/federation/v1/make_join/:room_id/:user_id",
396//     }
397// };
398
399pub fn make_join_request(origin: &str, args: MakeJoinReqArgs) -> SendResult<SendRequest> {
400    let ver = args.ver.iter().map(|v| format!("ver={v}")).join("&");
401    let ver = if ver.is_empty() { "" } else { &*format!("?{}", ver) };
402
403    let url = Url::parse(&format!(
404        "{origin}/_matrix/federation/v1/make_join/{}/{}{}",
405        args.room_id, args.user_id, ver
406    ))?;
407    Ok(crate::sending::get(url))
408}
409
410/// Request type for the `create_join_event_template` endpoint.
411#[derive(ToParameters, Deserialize, Debug)]
412pub struct MakeJoinReqArgs {
413    /// The room ID that is about to be joined.
414    #[salvo(parameter(parameter_in = Path))]
415    pub room_id: OwnedRoomId,
416
417    /// The user ID the join event will be for.
418    #[salvo(parameter(parameter_in = Path))]
419    pub user_id: OwnedUserId,
420
421    /// The room versions the sending server has support for.
422    ///
423    /// Defaults to `&[RoomVersionId::V1]`.
424    #[salvo(parameter(parameter_in = Query))]
425    #[serde(default = "default_ver", skip_serializing_if = "is_default_ver")]
426    pub ver: Vec<RoomVersionId>,
427}
428
429/// Response type for the `create_join_event_template` endpoint.
430#[derive(ToSchema, Serialize, Deserialize, Debug)]
431
432pub struct MakeJoinResBody {
433    /// The version of the room where the server is trying to join.
434    #[serde(skip_serializing_if = "Option::is_none")]
435    pub room_version: Option<RoomVersionId>,
436
437    /// An unsigned template event.
438    #[salvo(schema(value_type = Object, additional_properties = true))]
439    pub event: Box<RawJsonValue>,
440}
441
442impl MakeJoinResBody {
443    /// Creates a new `Response` with the given template event.
444    pub fn new(event: Box<RawJsonValue>) -> Self {
445        Self {
446            room_version: None,
447            event,
448        }
449    }
450}
451
452fn default_ver() -> Vec<RoomVersionId> {
453    vec![RoomVersionId::V1]
454}
455
456fn is_default_ver(ver: &[RoomVersionId]) -> bool {
457    *ver == [RoomVersionId::V1]
458}