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}