palpo_core/appservice/event.rs
1//! Endpoint for sending events.
2
3//! `PUT /_matrix/app/*/transactions/{txn_id}`
4//!
5//! Endpoint to push an event (or batch of events) to the application service.
6//! `/v1/` ([spec])
7//!
8//! [spec]: https://spec.matrix.org/latest/application-service-api/#put_matrixappv1transactionstxnid
9
10use reqwest::Url;
11use salvo::oapi::ToSchema;
12use serde::{Deserialize, Deserializer, Serialize};
13
14use crate::events::AnyTimelineEvent;
15use crate::events::receipt::ReceiptContent;
16use crate::presence::PresenceContent;
17use crate::sending::{SendRequest, SendResult};
18use crate::serde::from_raw_json_value;
19use crate::{JsonValue, OwnedRoomId, OwnedUserId, RawJsonValue, serde::RawJson};
20
21/// `PUT /_matrix/app/*/transactions/{txn_id}`
22///
23/// Endpoint to push an event (or batch of events) to the application service.
24/// `/v1/` ([spec])
25///
26/// [spec]: https://spec.matrix.org/latest/application-service-api/#put_matrixappv1transactionstxnid
27
28// const METADATA: Metadata = metadata! {
29// method: PUT,
30// rate_limited: false,
31// authentication: AccessToken,
32// history: {
33// 1.0 => "/_matrix/app/v1/transactions/:txn_id",
34// }
35// };
36
37pub fn push_events_request(origin: &str, txn_id: &str, body: PushEventsReqBody) -> SendResult<SendRequest> {
38 let url = Url::parse(&format!("{origin}/_matrix/app/v1/transactions/{}", txn_id))?;
39 crate::sending::post(url).stuff(body)
40}
41/// Request type for the `push_events` endpoint.
42
43#[derive(ToSchema, Deserialize, Serialize, Debug)]
44pub struct PushEventsReqBody {
45 /// The transaction ID for this set of events.
46 ///
47 /// HomeServers generate these IDs and they are used to ensure idempotency of results.
48 // #[salvo(parameter(parameter_in = Path))]
49 // pub txn_id: OwnedTransactionId,
50
51 /// A list of events.
52 pub events: Vec<RawJson<AnyTimelineEvent>>,
53 // /// Information on E2E device updates.
54 // #[serde(
55 // default,
56 // skip_serializing_if = "DeviceLists::is_empty",
57 // rename = "org.matrix.msc3202.device_lists"
58 // )]
59 // pub device_lists: DeviceLists,
60
61 // /// The number of unclaimed one-time keys currently held on the server for this device, for
62 // /// each algorithm.
63 // #[serde(
64 // default,
65 // skip_serializing_if = "BTreeMap::is_empty",
66 // rename = "org.matrix.msc3202.device_one_time_keys_count"
67 // )]
68 // pub device_one_time_keys_count: BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, BTreeMap<DeviceKeyAlgorithm, u64>>>,
69
70 // /// A list of key algorithms for which the server has an unused fallback key for the
71 // /// device.
72 // #[serde(
73 // default,
74 // skip_serializing_if = "BTreeMap::is_empty",
75 // rename = "org.matrix.msc3202.device_unused_fallback_key_types"
76 // )]
77 // pub device_unused_fallback_key_types: BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, Vec<DeviceKeyAlgorithm>>>,
78
79 // /// A list of EDUs.
80 // #[serde(
81 // default,
82 // skip_serializing_if = "<[_]>::is_empty",
83 // rename = "de.sorunome.msc2409.ephemeral"
84 // )]
85 // pub ephemeral: Vec<Edu>,
86
87 // /// A list of to-device messages.
88
89 // #[serde(
90 // default,
91 // skip_serializing_if = "<[_]>::is_empty",
92 // rename = "de.sorunome.msc2409.to_device"
93 // )]
94 // pub to_device: Vec<RawJson<AnyToDeviceEvent>>,
95}
96crate::json_body_modifier!(PushEventsReqBody);
97
98/// Type for passing ephemeral data to homeservers.
99
100#[derive(ToSchema, Clone, Debug, Serialize)]
101#[non_exhaustive]
102pub enum Edu {
103 /// An EDU representing presence updates for users of the sending homeserver.
104 Presence(PresenceContent),
105
106 /// An EDU representing receipt updates for users of the sending homeserver.
107 #[salvo(schema(value_type = Object))]
108 Receipt(ReceiptContent),
109
110 /// A typing notification EDU for a user in a room.
111 Typing(TypingContent),
112
113 #[doc(hidden)]
114 #[salvo(schema(skip))]
115 _Custom(JsonValue),
116}
117
118#[derive(Debug, Deserialize)]
119
120struct EduDeHelper {
121 /// The message type field
122 r#type: String,
123 content: Box<RawJsonValue>,
124}
125
126impl<'de> Deserialize<'de> for Edu {
127 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
128 where
129 D: Deserializer<'de>,
130 {
131 let json = Box::<RawJsonValue>::deserialize(deserializer)?;
132 let EduDeHelper { r#type, content } = from_raw_json_value(&json)?;
133
134 Ok(match r#type.as_ref() {
135 "m.presence" => Self::Presence(from_raw_json_value(&content)?),
136 "m.receipt" => Self::Receipt(from_raw_json_value(&content)?),
137 "m.typing" => Self::Typing(from_raw_json_value(&content)?),
138 _ => Self::_Custom(from_raw_json_value(&content)?),
139 })
140 }
141}
142
143/// The content for "m.typing" Edu.
144
145#[derive(ToSchema, Deserialize, Serialize, Clone, Debug)]
146pub struct TypingContent {
147 /// The room where the user's typing status has been updated.
148 pub room_id: OwnedRoomId,
149
150 /// The user ID that has had their typing status changed.
151 pub user_id: OwnedUserId,
152
153 /// Whether the user is typing in the room or not.
154 pub typing: bool,
155}
156
157impl TypingContent {
158 /// Creates a new `TypingContent`.
159 pub fn new(room_id: OwnedRoomId, user_id: OwnedUserId, typing: bool) -> Self {
160 Self {
161 room_id,
162 user_id,
163 typing,
164 }
165 }
166}