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}