Skip to main content

matrix_bot_sdk/models/
events.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use super::{PowerLevels, Presence};
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
7#[serde(rename_all = "snake_case")]
8pub enum EventKind {
9    RoomMessage,
10    RoomMember,
11    RoomAliases,
12    RoomCanonicalAlias,
13    RoomCreate,
14    RoomJoinRules,
15    RoomPowerLevels,
16    RoomRedaction,
17    RoomPinnedEvents,
18    RoomAvatar,
19    RoomName,
20    RoomTopic,
21    SpaceChild,
22    RoomEncryption,
23    RoomEncrypted,
24    Presence,
25    Custom(String),
26}
27
28impl Default for EventKind {
29    fn default() -> Self {
30        Self::Custom("m.custom".to_owned())
31    }
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
35pub struct Event {
36    #[serde(default)]
37    pub event_id: String,
38    #[serde(rename = "type")]
39    pub kind: EventKind,
40    #[serde(default)]
41    pub sender: String,
42    #[serde(default)]
43    pub origin_server_ts: i64,
44    #[serde(default)]
45    pub content: Value,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
49pub struct RoomEvent {
50    pub room_id: String,
51    #[serde(flatten)]
52    pub event: Event,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
56pub struct StateEvent {
57    #[serde(flatten)]
58    pub room_event: RoomEvent,
59    pub state_key: String,
60}
61
62#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
63#[serde(rename_all = "snake_case")]
64pub enum Membership {
65    Invite,
66    Join,
67    Knock,
68    #[default]
69    Leave,
70    Ban,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
74pub struct MembershipEvent {
75    pub user_id: String,
76    pub membership: Membership,
77}
78
79impl MembershipEvent {
80    pub fn effective_membership(&self) -> Membership {
81        match self.membership {
82            Membership::Ban => Membership::Leave,
83            other => other,
84        }
85    }
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
89pub struct MessageEvent {
90    pub msgtype: String,
91    pub body: String,
92    #[serde(default)]
93    pub formatted_body: Option<String>,
94    #[serde(default)]
95    pub format: Option<String>,
96    #[serde(default)]
97    pub url: Option<String>,
98    #[serde(default)]
99    pub info: Option<serde_json::Value>,
100    #[serde(default)]
101    pub file: Option<EncryptedFile>,
102}
103
104/// File information for media messages.
105#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
106pub struct FileInfo {
107    #[serde(default)]
108    pub mimetype: Option<String>,
109    #[serde(default)]
110    pub size: Option<u64>,
111}
112
113/// Thumbnail information for media messages.
114#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
115pub struct ThumbnailInfo {
116    #[serde(flatten)]
117    #[serde(default)]
118    pub info: FileInfo,
119    #[serde(default)]
120    pub width: Option<u64>,
121    #[serde(default)]
122    pub height: Option<u64>,
123    #[serde(default)]
124    pub orientation: Option<u64>,
125}
126
127/// Image information for media messages.
128#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
129pub struct ImageInfo {
130    #[serde(flatten)]
131    #[serde(default)]
132    pub info: FileInfo,
133    #[serde(default)]
134    pub width: Option<u64>,
135    #[serde(default)]
136    pub height: Option<u64>,
137    #[serde(default)]
138    pub orientation: Option<u64>,
139}
140
141/// Video information for media messages.
142#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
143pub struct VideoInfo {
144    #[serde(flatten)]
145    #[serde(default)]
146    pub info: ImageInfo,
147    #[serde(default)]
148    pub duration: Option<u64>,
149}
150
151/// Audio information for media messages.
152#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
153pub struct AudioInfo {
154    #[serde(flatten)]
155    #[serde(default)]
156    pub info: FileInfo,
157    #[serde(default)]
158    pub duration: Option<u64>,
159}
160
161/// Location information for location messages.
162#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
163pub struct LocationInfo {
164    #[serde(default)]
165    pub uri: Option<String>,
166    #[serde(default)]
167    pub description: Option<String>,
168}
169
170/// Encrypted file information.
171#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
172pub struct EncryptedFile {
173    pub url: String,
174    pub key: JsonWebKey,
175    pub iv: String,
176    #[serde(default)]
177    pub hashes: std::collections::HashMap<String, String>,
178    pub version: String,
179}
180
181/// JSON Web Key for encrypted files.
182#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
183pub struct JsonWebKey {
184    pub kty: String,
185    #[serde(default)]
186    pub alg: Option<String>,
187    pub k: String,
188    #[serde(default)]
189    pub ext: Option<bool>,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
193pub struct AliasesEvent {
194    #[serde(default)]
195    pub aliases: Vec<String>,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
199pub struct CanonicalAliasEvent {
200    #[serde(default)]
201    pub alias: Option<String>,
202    #[serde(default)]
203    pub alt_aliases: Vec<String>,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
207pub struct CreateEvent {
208    pub creator: String,
209    #[serde(default)]
210    pub room_version: Option<String>,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
214#[serde(rename_all = "snake_case")]
215pub enum JoinRule {
216    Public,
217    #[default]
218    Invite,
219    Knock,
220    Private,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
224pub struct JoinRulesEvent {
225    pub join_rule: JoinRule,
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
229pub struct PowerLevelsEvent {
230    #[serde(flatten)]
231    pub power_levels: PowerLevels,
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
235pub struct RedactionEvent {
236    pub redacts: String,
237    #[serde(default)]
238    pub reason: Option<String>,
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
242pub struct PinnedEventsEvent {
243    #[serde(default)]
244    pub pinned: Vec<String>,
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
248pub struct RoomAvatarEvent {
249    #[serde(default)]
250    pub url: Option<String>,
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
254pub struct RoomNameEvent {
255    #[serde(default)]
256    pub name: Option<String>,
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
260pub struct RoomTopicEvent {
261    #[serde(default)]
262    pub topic: Option<String>,
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
266pub struct SpaceChildEvent {
267    pub child_room_id: String,
268    #[serde(default)]
269    pub via: Vec<String>,
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
273pub struct EncryptionEvent {
274    pub algorithm: String,
275    #[serde(default)]
276    pub rotation_period_ms: Option<u64>,
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
280pub struct EncryptedRoomEvent {
281    pub algorithm: String,
282    pub sender_key: String,
283    pub ciphertext: String,
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
287pub struct PresenceEvent {
288    pub user_id: String,
289    pub presence: Presence,
290    #[serde(default)]
291    pub status_msg: Option<String>,
292}
293
294#[derive(Debug, thiserror::Error)]
295pub enum InvalidEventError {
296    #[error("missing field: {0}")]
297    MissingField(&'static str),
298    #[error("invalid event kind")]
299    InvalidType,
300    #[error("invalid json: {0}")]
301    InvalidJson(#[from] serde_json::Error),
302}
303
304/// Error indicating that a Matrix event has been redacted and its content is no longer available.
305#[derive(Debug, Clone, thiserror::Error, Serialize, Deserialize, PartialEq, Eq)]
306#[error("Event {event_id} was redacted")]
307pub struct EventRedactedError {
308    /// The ID of the redacted event.
309    pub event_id: String,
310    /// The reason for the redaction, if provided.
311    #[serde(skip_serializing_if = "Option::is_none")]
312    pub reason: Option<String>,
313}
314
315impl EventRedactedError {
316    pub fn new(event_id: impl Into<String>) -> Self {
317        Self {
318            event_id: event_id.into(),
319            reason: None,
320        }
321    }
322
323    pub fn with_reason(event_id: impl Into<String>, reason: impl Into<String>) -> Self {
324        Self {
325            event_id: event_id.into(),
326            reason: Some(reason.into()),
327        }
328    }
329}
330
331pub mod converter {
332    use serde_json::Value;
333
334    use super::{Event, EventKind, InvalidEventError, RoomEvent};
335
336    pub fn parse_event(json: &Value) -> Result<Event, InvalidEventError> {
337        let kind_raw = json
338            .get("type")
339            .and_then(Value::as_str)
340            .ok_or(InvalidEventError::MissingField("type"))?;
341        let kind = match kind_raw {
342            "m.room.message" => EventKind::RoomMessage,
343            "m.room.member" => EventKind::RoomMember,
344            "m.room.aliases" => EventKind::RoomAliases,
345            "m.room.canonical_alias" => EventKind::RoomCanonicalAlias,
346            "m.room.create" => EventKind::RoomCreate,
347            "m.room.join_rules" => EventKind::RoomJoinRules,
348            "m.room.power_levels" => EventKind::RoomPowerLevels,
349            "m.room.redaction" => EventKind::RoomRedaction,
350            "m.room.pinned_events" => EventKind::RoomPinnedEvents,
351            "m.room.avatar" => EventKind::RoomAvatar,
352            "m.room.name" => EventKind::RoomName,
353            "m.room.topic" => EventKind::RoomTopic,
354            "m.space.child" => EventKind::SpaceChild,
355            "m.room.encryption" => EventKind::RoomEncryption,
356            "m.room.encrypted" => EventKind::RoomEncrypted,
357            "m.presence" => EventKind::Presence,
358            other => EventKind::Custom(other.to_owned()),
359        };
360
361        Ok(Event {
362            event_id: json
363                .get("event_id")
364                .and_then(Value::as_str)
365                .unwrap_or_default()
366                .to_owned(),
367            kind,
368            sender: json
369                .get("sender")
370                .and_then(Value::as_str)
371                .unwrap_or_default()
372                .to_owned(),
373            origin_server_ts: json
374                .get("origin_server_ts")
375                .and_then(Value::as_i64)
376                .unwrap_or_default(),
377            content: json.get("content").cloned().unwrap_or(Value::Null),
378        })
379    }
380
381    pub fn parse_room_event(
382        json: &Value,
383        room_id: impl Into<String>,
384    ) -> Result<RoomEvent, InvalidEventError> {
385        Ok(RoomEvent {
386            room_id: room_id.into(),
387            event: parse_event(json)?,
388        })
389    }
390}